Base-GPU资源屏障与同步
GPU 命令按顺序提交,不意味着所有阶段都逐条执行完才开始下一条。流水线重叠、缓存和多个队列,使资源访问之间需要显式约束。
资源屏障描述“谁产生了什么、谁接下来要怎样使用它”。本文以 D3D12 的传统资源屏障模型为主,并吸收 2026 年 3 月 31 日问答笔记中的同步问题;Vulkan 的阶段掩码、访问掩码和图像布局不能直接翻译成同名枚举。
1. 依赖包括执行与内存
假设 Pass A 写颜色纹理,Pass B 读取它。B 不仅需要等待相关写入完成,还需要看到正确的数据;若资源布局或使用状态有要求,也要完成相应转换。
把屏障理解为“GPU 全部停下来”太宽泛。它约束的是指定资源、阶段与访问之间的依赖,实际代价可能涉及等待、缓存操作或布局处理。无关工作仍可能有重叠空间。
2. D3D12 的三类传统屏障
| 类型 | 主要解决的问题 | 例子 |
|---|---|---|
| Transition | 子资源用途状态变化 | 渲染目标写完后作为像素 Shader 输入 |
| UAV | UAV 访问之间需要有序可见 | 一次 Dispatch 的输出作为下一次输入 |
| Aliasing | 不同资源复用重叠堆内存 | 生命周期结束后切换到另一逻辑资源 |
深度附件通常以 DEPTH_WRITE 写入,不应把 ShadowMap 统一记成颜色附件的 RENDER_TARGET。纹理和 Buffer 的合法状态也需要结合实际用途。
3. 看“读写关系”,不能只数 Pass
写后读是最直观的依赖。写后写也可能需要保证顺序,尤其是后一次写覆盖、累加或复用前一次结果时;但两个相同 RenderTarget 状态之间并不是总要额外插入一次状态转换。
反过来,状态不变也不代表没有同步问题:资源一直处于 UAV 状态,连续 Dispatch 仍可能需要 UAV 屏障。状态转换与访问有序性是相关但不等同的概念。
只读之间通常不产生数据依赖,但布局兼容、队列所有权及资源生命周期仍可能带来其他约束。
4. 线程组同步管不到整个 GPU
GroupMemoryBarrierWithGroupSync() 面向线程组中的共享内存访问与组内会合,不能拿来同步整个 Dispatch 的所有组,更不能同步另一个 GPU 队列。
同一组所有需要参与的线程必须到达组同步点。在部分线程提前返回、其余线程等待的代码里使用同步,会产生未定义或不可靠行为。
跨 Dispatch 的阶段划分、跨队列的 Fence/信号量,以及 CPU 等 GPU 的完成通知,都属于不同范围的同步。不要用一个“Barrier”词把它们混在一起。
5. RenderGraph 能做什么
图系统知道 Pass 读写关系后,可以推导生命周期与依赖,在后端生成必要同步。但它只能推导已经声明的信息:图外修改资源、漏报 UAV 读写或隐式副作用,都可能使计划失效。
CPU 上并行录制命令也不等于 GPU 可以并行执行。即使 A 与 B 的录制可以独立,B 的执行仍可能依赖 A 的数据。应分别讨论录制任务依赖和 GPU 资源依赖。
6. 优化目标是缩小必要约束
优先减少不必要的中间结果与立即回读,再检查屏障范围是否过宽、是否存在可重排的无关工作,以及相邻状态变化能否批量表达。批量调用可以降低管理开销,但不会自动删除真实的数据依赖。
不要通过“少插一个试试”修性能。先启用 API 验证,再用 GPU 抓帧观察等待、带宽和队列利用率。某个驱动上暂时未复现错误,不能证明资源访问合法。
总结
资源状态、执行先后、内存可见性与队列完成是四个需要分别说清的层面。正确的同步是优化的前提;更精确地表达依赖,才可能在保证结果一致的同时保留更多流水线重叠。