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

Synchronization -- Swapchain Semaphore Reuse

问题说明

vkQueuePresentKHR的等待信号量很容易被误用,但幸运的是,修复方法通常也很简单:为每个交换链图像分配一个 “提交完成” 信号量,而不是按帧缓冲数量分配。

本章讨论一种安全复用vkQueuePresentKHR等待信号量(即VkPresentInfoKHR::pWaitSemaphores中指定的信号量)的方式。这里的 “安全” 指 Vulkan 规范保证信号量已不再被使用,可以被复用。

从 Vulkan SDK 1.4.313 开始,校验层会报告 present 等待信号量未被安全使用的情况。该问题通常以VUID-vkQueueSubmit-pSignalSemaphores-00067报错出现,或提示:your VkSemaphore is being signaled by VkQueue, but it may still be in use by VkSwapchainKHR

首先我们需要理解 present 等待信号量的特殊性。Vulkan 同步的核心是执行顺序,通过等待信号、轮询等方式实现。我们等待的对象必须能发出状态变更信号或提供查询机制。

例如:

  • vkQueueSubmit可指定一个 fence,当工作负载完成时触发,应用可在 CPU(主机)侧等待该 fence。
  • vkQueueSubmit也可触发信号量,这些信号量仅能在 GPU(设备)侧等待(时间线信号量可在主机侧等待)。

vkQueuePresentKHRvkQueueSubmit系列函数不同:它不提供触发信号量或 fence 的机制(无额外扩展时)。这意味着:

  1. 无法直接等待 present 完成信号。
  2. 无法知道VkPresentInfoKHR::pWaitSemaphores是否仍被呈现操作占用。

如果vkQueuePresentKHR可以触发信号,等待该信号即可确认 present 队列操作已完成(包括对VkPresentInfoKHR::pWaitSemaphores的等待)。

总结:何时可以安全复用 present 等待信号量并不直观


解决方案讨论

好消息是:存在一种简单方法能保证呈现操作已完成,尽管不如显式等待直接。

通过vkAcquireNextImageKHR获取图像索引,然后等待其信号量或 fence,即可保证使用该图像索引的上一次呈现操作已完成,其中包括对VkPresentInfoKHR::pWaitSemaphores的等待,因此对应信号量可以安全复用。

安全复用 present 等待信号量的流程:

  1. 调用vkAcquireNextImageKHR获取图像索引。
  2. 在某一批vkQueueSubmit中等待来自vkAcquireNextImageKHR的信号量(也可等待 fence,但会引入额外主机同步点)。
  3. 等待完成后即可安全复用信号量。

为什么很多应用会出错?原因很可能是:只要稍微偏离 “你触发、我等待” 的最直观同步方式,复杂度就会上升,逻辑变得不明显,进而容易出错。

常见错误:按帧缓冲数量(如双缓冲、三缓冲)管理 present 等待信号量。应用通常用vkQueueSubmit的 fence 同步帧缓冲,等待该 fence 即可确认对应命令缓冲与帧资源已不再使用。但 Vulkan 规范不保证等待vkQueueSubmit的 fence 也能同步呈现操作。

呈现资源的复用应依赖vkAcquireNextImageKHR或额外扩展(文末提及),而非vkQueueSubmit的 fence。

以下伪代码展示常见错误(在特定驱动上可能运行正常,但违反 Vulkan 规范):

c

运行

// !! 错误代码示例 !! const kNumberOfFramesInFlight = 2 VkSemaphore submit_semaphores[kNumberOfFramesInFlight] while (!quit) { // 等待帧 fence // 这允许复用帧资源,但不包括呈现资源 VkFence frame_fence = frame_fences[frame_index] vkWaitForFences(frame_fence) vkResetFences(frame_fence) ... // 警告:此处按当前帧缓冲索引获取未使用的提交信号量 // 通常假设等待上一帧后,submit_semaphores 就不再被该帧的 vkQueuePresentKHR 使用 // 这不一定成立 VkSemaphore submit_semaphore = submit_semaphores[frame_index] VkSubmitInfo submit_info submit_info.pSignalSemaphores = &submit_semaphore vkQueueSubmit(queue, &submit_info) // 警告:submit_semaphore 可能仍被之前的某一次呈现操作占用 VkPresentInfo present_info present_info.pWaitSemaphores = &submit_semaphore vkQueuePresentKHR(queue, &present_info) frame_index = (frame_index + 1) % kNumberOfFramesInFlight }

修复方法非常简单:

  1. submit_semaphores数组按交换链图像数量分配(而非帧缓冲数量)。
  2. 使用获取到的交换链图像索引访问该数组(而非当前帧缓冲索引)。

在修复 vkcube 这类简单应用时,修复步骤就像上面描述的一样直接。对于复杂引擎架构则可能有所不同。

以下伪代码展示正确复用呈现等待信号量的渲染帧流程,适用于所有 Vulkan 版本:

// !! 正确代码示例 !! VkImage swapchain_images[num_swapchain_images] // 按当前帧缓冲索引管理的资源 const kNumberOfFramesInFlight = 2 VkFence frame_fences[kNumberOfFramesInFlight]; VkSemaphore acquire_semaphores[kNumberOfFramesInFlight]; VkCommandBuffer command_buffers[kNumberOfFramesInFlight]; int frame_index = 0; // 0..kNumberOfFramesInFlight-1 // 被 QueuePresent 等待的信号量按交换链图像数量缓冲 VkSemaphore submit_semaphores[swapchain_image_count] while (!quit) { VkFence frame_fence = frame_fences[frame_index] vkWaitForFences(frame_fence) vkResetFences(frame_fence) uint32_t image_index; VkSemaphore acquire_semaphore = acquire_semaphores[frame_index] vkAcquireNextImageKHR(swapchain, acquire_semaphore, &image_index) // 用获取到的交换链图像索引访问提交信号量 // 本例中这是唯一用 image_index 索引的资源 // 其他所有资源(包括 acquire_semaphore)都用当前帧缓冲索引 VkSemaphore submit_semaphore = submit_semaphores[image_index] VkCommandBuffer command_buffer = command_buffers[frame_index] vkBeginCommandBuffer(command_buffer) RecordCommands(command_buffer) vkEndCommandBuffer(command_buffer) VkSubmitInfo submit_info submit_info.pWaitSemaphores = &acquire_semaphore submit_info.pCommandBuffers = &command_buffer submit_info.pSignalSemaphores = &submit_semaphore vkQueueSubmit(queue, &submit_info, frame_fence) VkPresentInfo present_info present_info.pWaitSemaphores = &submit_semaphore present_info.pSwapchains = &swapchain present_info.pImageIndices = &image_index vkQueuePresent(queue, &present_info) frame_index = (frame_index + 1) % kNumberOfFramesInFlight }

VK_EXT_swapchain_maintenance1 扩展

上面的代码用于说明不依赖额外扩展如何处理交换链等待信号量。支持VK_EXT_swapchain_maintenance1的实现提供了另一种方案。

该扩展让vkQueuePresentKHR更接近vkQueueSubmit,允许指定一个应用可等待的 fence。

VK_EXT_swapchain_maintenance1还解决了原生 Vulkan 中没有良好方案的问题:退出时释放交换链资源

通常应用会调用vkDeviceWaitIdlevkQueueWaitIdle,并认为此时删除交换链信号量与交换链本身是安全的。问题在于:WaitIdle函数基于 fence 定义 —— 它们只等待通过带 fence 参数的函数提交的工作负载。原生vkQueuePresent不提供 fence 参数。

理论上,这意味着vkDeviceWaitIdle无法保证释放交换链资源是安全的。实践中应用仍会这样做,因为没有更好选择。这也是校验层在此场景下不会报错的原因。

VK_EXT_swapchain_maintenance1扩展修复了该问题。通过等待呈现 fence,应用可安全释放交换链资源。启用该扩展后,若应用退出流程依赖vkDeviceWaitIdlevkQueueWaitIdle释放交换链资源,而非使用呈现 fence,校验层会报错。

http://www.jsqmd.com/news/504782/

相关文章:

  • 3个核心功能:Sketchfab技术解析与高效获取方案
  • 影视仓内置本地包避坑指南:常见编译错误与接口语法详解(2024.12.27版本)
  • 终端党必备:用Swift+Vision实现命令行图片文字识别(支持多语言切换)
  • 文件太大怎么办?教你用 7Z 分卷打包
  • MATLAB实战:从OBJ文件到3D模型的可视化处理全流程
  • 74HC595避坑指南:LED点阵显示残影消除的3种实战方法
  • 达梦数据库MERGE语句实战:如何解决数据转换丢失警告(DEC长度超限)
  • Nanbeige 4.1-3B算力优化:@st.cache_resource缓存机制深度解析
  • [Java]查找算法排序算法
  • COZE - 3
  • 2026年热门的定制服务器品牌推荐:企业级NAS存储服务器可靠供应商推荐 - 品牌宣传支持者
  • Rust实战指南:从枚举到错误处理的进阶技巧
  • Kiro AWS Observability Power 配置与使用指南
  • java内部类
  • 技术小白也能懂:什么是代理IP池?怎么买不踩坑?
  • Dify报错“RateLimitExceeded”却查不到源头?资深架构师拆解5层Token计费穿透追踪术(含OpenTelemetry埋点模板)
  • Base62编码实战:用C语言手把手实现短链接生成器(附完整源码)
  • 突破软件功能限制:从评估模式到全功能体验的技术路径
  • 统信UOS外接显示器黑屏?5步搞定NVIDIA驱动配置(附BusID查找技巧)
  • EagleEye DAMO-YOLO TinyNAS应用:三步实现产品质量视觉检测
  • 2026年环卫服务优质服务商推荐榜:单位环卫/四川环卫公司/四川环卫资质公司/小区环卫/市政环卫/环卫工程/环卫资质公司/选择指南 - 优质品牌商家
  • 异步电机参数解析:从铭牌数据到等效电路的公式法实践
  • 从普通人视角看“移动云盘拉新”:模式、渠道与可行性分析
  • 负荷需求响应matlab 考虑电价需求弹性系数矩阵的负荷需求响应,采用matlab进行编程
  • ROS1仿真调试:解析TF_REPEATED_DATA警告与时间戳冲突的实战指南
  • Snort入侵检测实战:5分钟为你的Web服务器配置DDoS攻击告警规则
  • Beyond Compare 5 密钥生成完整指南:两种方法快速激活软件授权
  • PX4飞控解锁失败?别慌!排查CBRK_USB_CHK等关键参数与常见传感器报错
  • FreeRTOS-任务通知-1
  • Pinia持久化插件persist深度解析:从原理到最佳实践