项目实训记录:兼容性测试与边界问题
一、从“能跑”到“跑得稳”
上篇联调结束的时候,两个 Vulkan 官方示例跑通了,validation layer 没报警,我一度觉得这事差不多了。结果换了个示例一跑,崩了。
之后几天我陆续测了六个官方示例,碰上各种各样没预料到的情况。现在回头看,两个示例跑通只代表“系统在一种特定渲染管线下是通的”,跟“兼容多种管线”差了十万八千里。这篇把测试过程、碰到的问题、怎么修的,如实记下来。
二、测试策略
Vulkan 官方示例有几十个,全测一遍不现实。我按三个维度挑了几个有代表性的:
-
直接渲染到 swapchain:triangle(最简管线)、texture(带纹理采样)
-
渲染到中间纹理再 copy:offscreen(离屏渲染后 copy 到 swapchain)
-
多 pass / 复杂后处理:bloom(多轮降采样 + 合成)、deferred(多 render target + 组合)
目标不是“测很多个”,而是“覆盖几种不同的渲染结构”。测之前先列了一张表,记每个示例的预期表现——swapchain usage 有没有 transfer 位、会不会走回退、swapchain 重建几率大不大。这样测完有对比基准。
三、测试记录
3.1 triangle(直接渲染到 swapchain)
最简的单 pass 示例。渲染直接画到 swapchain image 上,没有中间纹理,没有后处理。
-
swapchain usage:
COLOR_ATTACHMENT,没有 transfer 位 -
触发回退:是,
CanUseTransfer返回 false,走清屏路径 -
表现:清屏正常,画面被覆盖为纯色。帧率无明显波动
3.2 texture(直接渲染 + 纹理采样)
比 triangle 多了一个纹理绑定,但渲染目标仍是 swapchain。
-
swapchain usage:
COLOR_ATTACHMENT,同样没有 transfer 位 -
触发回退:是,走清屏
-
表现:同 triangle,正常清屏
这两个示例验证了回退路径没问题。但 transfer 位一直没开,阶段 2 的缩放链路在这两个示例上没法验。
3.3 offscreen(渲染到中间纹理再 copy 到 swapchain)
这个示例的结构是先用一个 render pass 画到一张中间图,再用 vkCmdBlitImage 把中间图 copy 到 swapchain image 上。因为需要 blit,swapchain 创建时通常会带 transfer 位。
-
swapchain usage:
COLOR_ATTACHMENT | TRANSFER_SRC | TRANSFER_DST -
触发回退:否,transfer 位满足
-
阶段 2 生效:是,能看到缩放重采样的画面
-
问题:跑了大概三十秒,validation layer 报了一个 barrier 警告
警告内容:在缩放处理完成后,我把 swapchain image 从 TRANSFER_DST 转回 PRESENT_SRC_KHR。但这个示例本身紧接着还要做一次 vkCmdBlitImage——它是先把我处理过的 swapchain image 当 blit 的目标,我在 present 前把它转成 PRESENT_SRC 了,然后示例自己又把它当 TRANSFER_DST 用,layout 对不上。
这个问题的本质是:我不知道应用在 present 前还会不会对 swapchain image 做额外操作。offscreen 示例在 present 前有一次 blit,triangle 和 texture 没有。如果我一刀切地在 present 前把 layout 定死成 PRESENT_SRC,那些在 present 前还要用 swapchain image 的应用就会受影响。
修法:在注入处理结束后,不把 layout 转成 PRESENT_SRC,而是转回处理前的原始 layout。也就是在处理开始时先读一下当前的 layout,记下来,处理完再还原回去。这对 Manager 的要求高了一点——需要多记录一个 lastKnownLayout。目前暂时在 SwapchainData 里加了一个字段,每次 barrier 时更新。
// 处理前记录原始 layout
VkImageLayout originalLayout = sc.lastKnownLayout;
// ... barrier 转到 TRANSFER_DST,blit 处理 ...
// 处理后还原
barrier(cmdBuf, sc.images[imageIndex],
VK_IMAGE_LAYOUT_TRANSFER_DST, originalLayout);
sc.lastKnownLayout = originalLayout;
改完之后 offscreen 示例的警告消失。
3.4 bloom(多轮降采样 + 高斯模糊 + 合成)
这个示例的管线比较复杂:先渲染场景到一张 HDR 纹理,然后多次降采样做模糊,最后合成回 swapchain。降采样过程中会反复创建和销毁中间纹理,swapchain 的 usage 也开了 transfer 位。
-
swapchain usage:
COLOR_ATTACHMENT | TRANSFER_SRC | TRANSFER_DST -
触发回退:否
-
阶段 2 生效:是
-
问题:跑了一段时间后帧率明显下降,PIX 抓帧发现 GPU 时间没有异常增长,但 CPU 侧每帧耗时在变大
定位了半天,问题出在我 Manager 里的 GetDownscaleImage。每次调用它都检查一次中间纹理是否存在,我写检查逻辑的时候,CreateIntermediateImage 里有个 debug 日志用 vkGetImageMemoryRequirements 去查内存需求——这个调用本身就走了驱动,是个非零开销的操作。而 bloom 示例每帧 present 两次(两个 swapchain 或者两次提交),GetDownscaleImage 每帧被调了两遍,vkGetImageMemoryRequirements 也跟着调了两遍。
修法:中间纹理创建之后把 memory requirements 缓存起来,别每帧去查。改完帧率恢复正常。
3.5 deferred(多 render target + 延迟渲染)
这个示例用了 GBuffer——多个颜色附着同时写,后面再用 compute shader 做光照。swapchain usage 正常开了 transfer。
-
swapchain usage:
COLOR_ATTACHMENT | TRANSFER_SRC | TRANSFER_DST -
触发回退:否
-
阶段 2 生效:是
-
问题:画面闪烁,每隔几帧有一帧明显偏暗
这个问题排查了很久。deferred 示例的渲染流程里,swapchain image 在被 present 之前经历过 VK_IMAGE_LAYOUT_UNDEFINED → COLOR_ATTACHMENT → PRESENT_SRC 的转换(这是应用自己做的)。我的注入在 present 前又插了一步 PRESENT_SRC → TRANSFER_DST → TRANSFER_DST → PRESENT_SRC(加了一次还原之后变成这样)。
问题出在第一步:应用已经把 layout 转成 PRESENT_SRC 了,但它用的是 VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT → VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT 的 pipeline barrier。我的注入在同一个 queue 上提交,但没等这个 barrier 完成就读 swapchain image。偶发的闪烁是因为某些帧上 barrier 还没执行完我就开始 blit 了。
根源是我的注入提交用的是 VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT 等 semaphore,但 semaphore 只能保证“应用提交的命令执行完了”,不能保证“应用的 pipeline barrier 在 swapchain image 上生效了”。需要在我的 barrier 里等 COLOR_ATTACHMENT_OUTPUT 阶段。
这个修法在注入层,不在 Manager。但从定位问题的过程来看,多 pass 管线里的同步比我想的复杂一个级别。后续如果要接 compute shader 或 FSR2,barrier 的阶段选择得重新捋一遍。
四、兼容性测试小结
测试结果汇总:
| 示例 | 渲染结构 | Transfer | 阶段 | 稳定性 | 备注 |
|---|---|---|---|---|---|
| triangle | 直渲 swapchain | 无 | 阶段1清屏 | ✅ | 回退路径验证 |
| texture | 直渲 swapchain | 无 | 阶段1清屏 | ✅ | 回退路径验证 |
| offscreen | 中间图→blit→swapchain | 有 | 阶段2缩放 | ⚠️→✅ | layout 还原修了之后稳定 |
| bloom | 多轮降采样+合成 | 有 | 阶段2缩放 | ⚠️→✅ | 查内存需求缓存后恢复 |
| deferred | GBuffer+延迟光照 | 有 | 阶段2缩放 | ⚠️ | 偶发闪烁,疑似 barrier stage 不匹配 |
| hdr | HDR管线 | 有 | 阶段2缩放 | ✅ |
色彩空间转换正常,无异常 |
六个示例,四个完全稳定,一个修了之后稳定,一个还有偶发问题在查。坦白说比我想的差,但比联调刚结束时“以为两个就代表全部”的盲目乐观要真实得多。
五、从测试里抽象出来的规律
几个问题背后的共性,试着总结一下:
规律一:swapchain usage 是不能假设的。 六个示例里两个没开 transfer 位。这不是 bug,是 Vulkan 规范允许的。对中间件来说,只能检测不能假设,检测不到就走回退,没有第二条路。
规律二:layout 的处理不能是单向的。 最初“转成 TRANSFER_DST,用完转回 PRESENT_SRC”的思路,只在“应用在 present 前不再碰 swapchain image”的场景下成立。碰上 offscreen 这种自己也要用 swapchain 做 blit 目标的,就被打脸了。还原原始 layout 是一个更安全的选择。
规律三:中间件引入的每一条额外 API 调用,在高频路径上都会积累。 bloom 示例里一个 vkGetImageMemoryRequirements 就拖慢了帧率。在高频路径(每帧 present)上,非必要的驱动调用该砍就砍,结果该缓存就缓存。
规律四:同步问题的根因往往不在崩溃点。 deferred 示例的闪烁,表面看是画面暗了一帧,实际是 pipeline barrier 的阶段不对。这种问题在单 pass 的简单管线里永远暴露不出来。以后每加一种新的管线类型测试,都可能翻出新的同步隐患。
六、当前限制与遗留问题
测了一圈,系统的几个明确限制也清楚了:
-
swapchain usage 不含 transfer 时只能回退到清屏。 效果上完全不是“缩放”,只是一个可用性兜底。
-
多 queue 场景没有覆盖。 目前假设 present 和注入在同一个 queue 上。如果有应用用独立的 transfer queue 做 present,现在的同步链可能出错。
-
deferred 示例的偶发闪烁还没修完。 barrier stage 的调整方案已经有了,下个迭代改。
-
窗口 resize 的暴力测试没做。 持续拖拽窗口边缘快速 resize 的极端场景还没跑过。
-
只测了 Vulkan 官方示例。 真游戏引擎的管线复杂度至少高一个数量级。
七、下一步
测试暴露出来的问题里,最紧迫的是 deferred 的闪烁。先把这个修了,然后把窗口 resize 的极端场景跑一遍——快速拖拽、最大化最小化、alt-tab 来回切,这些在实际使用中太常见了。
性能方面也该打点了。现在只知道 bloom 示例里那个缓存问题修了,但整体注入开销到底是多少、锁等待占了多少、blit 本身占了多少,还没有量化数据。下一篇打算集中做性能打点和初步优化。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)