09-pixel-join-phase

第 9 篇:像素处理的汇合阶段(Pixel Processing — Join Phase)

原文:Part 9

一句话定位

并行完成的像素着色结果必须按 framebuffer 位置和 API 顺序重新汇合,经过 Late Z/Stencil、混合、压缩与内存读改写,最终成为可见图像。

Join 为什么困难

shader wave 的完成时间不一致:控制流、纹理 cache miss 和指令长度都会造成乱序。但同一像素位置上的混合和深度/模板更新是顺序敏感的读改写操作。硬件必须保留 primitive/batch 标签,缓存提前完成的结果,并只在安全时提交。

不相交的像素可以并行;真正需要排序的是可能作用于同一存储位置、且操作不满足交换律的更新。这也是屏幕分区能并行扩展 ROP 的原因。

ROP / Output Merger

flowchart LR S[乱序完成的 shader 结果] --> R[重排与位置分区] R --> Z[Late Z / Stencil] R --> B[Blend / Logic Op] M[目标/深度原值] --> Z M --> B Z --> W[压缩并写回] B --> W

ROP 是历史沿用的名称,现代职责通常包含颜色读写、混合、逻辑操作、部分深度/模板路径、格式转换和压缩。混合本身计算量不大,但要读取目标旧值并写回新值,因此经常受带宽、压缩和顺序制约。

若 blend 被禁用且可直接覆盖,硬件可能避免读取旧颜色;若颜色写掩码、逻辑操作或目标格式需要保留部分分量,仍可能需要读改写。不要只凭 API 状态猜实际流量,应结合架构与 counter。

布局、分块与 DRAM 局部性

framebuffer 常使用 tiled/swizzled 布局,使一个内存突发包含二维相邻像素,而不是长而窄的一行。这样 quad/tile 的访问更可能落在较少页面和缓存线中。ROP 分区也常按地址/屏幕区域分布,以并行利用多个内存通道。

这是一个贯穿全系列的设计回路:光栅器按 tile 产出 → shader 按 quad/wave 执行 → ROP 按 tile 聚合 → 内存布局让 tile 局部。各阶段的数据粒度相互配合。

颜色/深度压缩

无损 framebuffer 压缩减少读写带宽。快速清除用元数据表示统一值;其他压缩根据 tile 内颜色或深度的规律性保存紧凑形式。压缩状态是资源状态的一部分:某个消费者若不支持该格式/元数据,就要先展开或 resolve。

MSAA 增加每像素 sample 数据,压缩尤其重要。像素 shader 可按像素执行一次,而 ROP 仍需依据 coverage mask 对多个 sample 做深度、模板和颜色更新。

术语对照

英文 中文理解 关键点
join phase 汇合阶段 合并并行结果并恢复必要顺序
ROP 渲染输出单元/光栅操作单元 负责目标读改写等后端操作
output merger 输出合并 API 功能阶段名称
blending 混合 source 与 destination 的顺序敏感组合
read-modify-write 读改写 需要目标旧值,带宽和竞争敏感
render target compression 渲染目标压缩 无损减少内部/外部流量
resolve 解析/多采样解析 将辅助或多样本表示变成目标表示

面向 UMD/KMD 的阅读提示

  • UMD 负责 blend equation、write mask、RT format、sample state 与压缩/布局转换的硬件配置。
  • 追踪 attachment 生命周期:clear → render → sample/copy/present;记录每一步需要哪些 cache 操作或 resolve。
  • 检查 barrier 实现时,区分“等待先前工作”“刷新生产者 cache”“使消费者 cache 失效”“改变压缩/布局”四种动作。
  • KMD 可能负责显示扫描输出、显存迁移和跨队列同步,但通常不理解 blend 公式。

自测与实验

  • 为什么普通 alpha blending 难以对同一像素的两个 primitive 任意换序?
  • blend 关闭时,何种状态仍可能迫使硬件读取旧目标?
  • 比较线性布局与 tiled 布局,为什么后者通常更适合 2D 局部读改写?

本篇总结

像素后半程把“海量可乱序计算”收束为“对少量共享目标的有序更新”;ROP、分块布局和压缩共同决定它能否不被带宽拖住。

posted @ 2026-10-26 21:59  Sailxiv  阅读(2)  评论(0)    收藏  举报