6.1 栅栏与主机同步


文档摘要

6.1 栅栏与主机同步 本节摘要:栅栏是 CPU 唯一可以合法等待 GPU 的凭证。本节讲清栅栏的信号语义、阻塞等待与轮询查询两种消费姿势、按帧轮换的栅栏阵列,以及 vkDeviceWaitIdle 这个「大停顿」的正确使用场合——并顺着在途帧数把整册的节奏参数串起来。 栅栏(VkFence)的正式定义是:从 GPU 侧发信号、由 CPU 侧消费的同步原语。这个定义划出了它与其他同步工具的根本边界——凡是「CPU 需要知道 GPU 干完没有」的场合,只有栅栏一个合法通道。帧节奏控制、资源回收时机判定、上传完成确认,全是它的辖区。 一、信号语义与两种消费姿势 栅栏只有已发(signaled)与未发两种状态。

6.1 栅栏与主机同步

本节摘要:栅栏是 CPU 唯一可以合法等待 GPU 的凭证。本节讲清栅栏的信号语义、阻塞等待与轮询查询两种消费姿势、按帧轮换的栅栏阵列,以及 vkDeviceWaitIdle 这个「大停顿」的正确使用场合——并顺着在途帧数把整册的节奏参数串起来。

栅栏(VkFence)的正式定义是:从 GPU 侧发信号、由 CPU 侧消费的同步原语。这个定义划出了它与其他同步工具的根本边界——凡是「CPU 需要知道 GPU 干完没有」的场合,只有栅栏一个合法通道。帧节奏控制、资源回收时机判定、上传完成确认,全是它的辖区。

一、信号语义与两种消费姿势

栅栏只有已发(signaled)与未发两种状态。创建时可选 VK_FENCE_CREATE_SIGNALED_BIT 让它出生即已发——这个细节在帧循环设计里至关重要(稍后展开)。消费有两条路:

VkFenceCreateInfo fi = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO}; fi.flags = VK_FENCE_CREATE_SIGNALED_BIT; // 首帧即可通过等待 vkCreateFence(device, &fi, NULL, &frameFence); // 姿势一:阻塞等待(帧循环开头,预算明确时用) vkWaitForFences(device, 1, &frameFence, VK_TRUE, UINT64_MAX); vkResetFence(device, 1, &frameFence); // 用完必须手动复位 // 姿势二:轮询查询(主循环找活干时用) if (vkGetFenceStatus(device, uploadFence) == VK_SUCCESS) { // 上传完成:可以回收暂存缓冲了 }

两个容易忽略的规矩。其一,等待不会自动复位——vkWaitForFences 返回后栅栏仍处于已发态,不 reset 下次等待立刻通过,症状是「同步莫名失效」。其二,等待多个栅栏时 waitForAll 参数决定语义:VK_TRUE 等全部完成,VK_FALSE 等到任意一个完成即返回(配合轮询找出完成者)。

图 6-1:栅栏与信号量的时序分工

图 6-1:栅栏与信号量的时序分工

二、按帧轮换:栅栏阵列与出生即发

2.5 节埋的伏笔在此收线。在途帧数为三时,需要三条栅栏与三条缓冲一一配对,形成轮换:第 N 帧开头等的是第 N 减三帧的栅栏——它保证同一条命令缓冲的上一轮执行已结束,而不是所有 GPU 工作结束。出生即发(SIGNALED 位)的存在理由正在于此:首帧等待的栅栏从未被提交过信号,若以未发态出生,首帧会永远等下去。轮换下标用帧计数对在途数取模,与采集信号量、命令缓冲共用同一套下标——这套「在途套件」是所有 Vulkan 应用同步框架的心脏。

三、大停顿的正确场合:vkDeviceWaitIdle

vkWaitForFences 管单批工作,vkDeviceWaitIdle 管全局:等所有队列的所有工作完成。它是同步世界里最昂贵的调用,使用场合应当苛刻:交换链重建(2.4 案例)、销毁前清算、管线装配后的全局稳定点。帧循环内部出现它,几乎必然是设计问题——「每帧等一下图省事」会把流水线压缩成串行,性能回到三十年前。

资源销毁的时机判定是它的高级替身:不是等全局空闲,而是给待销毁资源挂「删除栅栏」,栅栏触发时再真正释放——延迟删除队列由此成为引擎标配,第 3 章案例里的「等确认无人使用再回收」就是它的实现。

四、案例:在途帧数调优实录

背景:某渲染器在途帧数初始设为二,高负载场景下 CPU 录制完就得等栅栏(GPU 未消化完),帧率卡在一个不上不下的值;开发者的第一反应是「GPU 不够快」。

操作:把节奏参数摊开测量。在途为二时,CPU 等栅栏的时间占比随负载上升逼近一半——瓶颈是「CPU 没活干」而非 GPU 慢。调整分两步:在途帧数提到三,CPU 每轮多一手余量,等待占比下降到可接受;展台数与同步套件同步扩到三(采集信号量、命令缓冲、uniform 分册全部联动),并确认交换链 minImageCount 支持。改完再做反向验证:降到一,帧率应声坍缩为纯串行。

结果:在途三的配置下 CPU 与 GPU 的忙碌曲线交替咬合,帧率提升约两成。参数从此写进配置并附注联动关系,避免后人只改一处。

解读:在途帧数是 CPU 与 GPU 流水线的「进纸格数」:太少流水线断流,太多放大延迟并占用三倍内存。它的正确值来自测量,而不是越 大越好——每多一手,所有按帧分册的资源(3.4)与同步对象(本节)都翻倍。

变式:CPU 极轻(录制即提交的简单场景)或 GPU 极重(离线渲染)的两端,在途数退化为二甚至一也无所谓;中间地带(录制与执行耗时接近)才是这个参数的价值区间。测量曲线是唯一的裁判。

五、延迟删除:栅栏的工程化形态

资源回收值得单独展开,因为它是最容易漏同步的地方。问题形态:纹理 T 在第 N 帧还被引用,CPU 在第 N 加一帧就销毁了它——销毁调用本身成功,几帧后驱动访问悬空资源,症状从花屏到崩溃不等。正确做法是延迟删除队列:销毁请求入队时附带当帧的栅栏(或帧序号),帧循环开头检查栅栏已触发才真正调用 vkDestroy。队列按资源类别分桶(缓冲、图像、描述符池各有销毁函数),统一由一个 GC 步骤驱动。

这套机制有三个设计要点。其一,挂靠对象要准:等的是「最后一次使用该资源的提交」对应的栅栏,不是「任意最近提交」——按帧序号对账比按时间猜可靠。其二,退出路径要清仓:程序退出前必须冲掉删除队列(等全部栅栏),否则清理器报泄漏。其三,内存告急时的妥协:删除队列堆积说明生产快于消费,此时主动等一手栅栏换内存安全,比无限堆积可取。

要点回顾前置:本节与 5.2 的状态机互为表里——「待执行态不可触碰」是规矩,「延迟删除队列」是执行规矩的机器。两者齐备,资源生命周期才算闭环。

六、要点回顾

  • 栅栏的独占管辖:CPU 等 GPU 只有栅栏一条合法通道,等待后必须手动复位。
  • 轮换套件:按在途帧数配齐栅栏、信号量、命令缓冲、uniform 分册,共用取模下标,出生即发位是首帧的通行证。
  • 等待两种姿势:阻塞等待用于节奏明确的帧头,轮询查询用于主循环捎带检查。
  • 大停顿要苛刻:vkDeviceWaitIdle 只属于重建与清算,帧内出现即设计问题;销毁用延迟删除队列替代。
  • 在途数靠测:它同时影响吞吐、延迟与内存,联动改、曲线裁。

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U