MAKE · WRITE · SHARE

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 抓帧观察等待、带宽和队列利用率。某个驱动上暂时未复现错误,不能证明资源访问合法。

总结

资源状态、执行先后、内存可见性与队列完成是四个需要分别说清的层面。正确的同步是优化的前提;更精确地表达依赖,才可能在保证结果一致的同时保留更多流水线重叠。

参考资料