Blog·Studio
文章系列日历归档关于搜索
Blog·Studio

一个记录思考、笔记与作品的技术博客。

Connect

© 2026 · Blog Studio

鄂ICP备19019526号

crafted with care

stay curious ✦

  1. 文章
  2. ›移动端 TBDR 渲染架构工程 2026:subpass 合并的三重代价

Index

  • 一、问题的提出:从桌面直推到手机 tile 的直觉崩塌
  • 二、形式化:TBDR 的两阶段模型与带宽算术
  • 三、Mali:subpass 合并的三重代价
  • 3.1 代价一:GPU 队列调度 —— binning 被强制串行化
  • 3.2 代价二:warp 调度 —— 依赖跟踪器的粒度决定 false dependency 密度
  • 3.3 代价三:功能单元吞吐与缓存 —— 把纹理换成 tile 反而更慢
  • 四、Adreno:FlexRender、Concurrent Binning 与 GMEM 视角
  • 4.1 FlexRender:当 tile-based 假设不成立时的自动退出
  • 4.2 Concurrent Binning:把 binning 从关键路径上摘下来
  • 4.3 GMEM 里的 Compute 与 per-tile draws
  • 五、可观测性:判断 subpass 是否真的合并了
  • 六、Vulkan 1.4 迁移:dynamic rendering local read 的时代
  • 七、帧调度、功耗与热:带宽优化最终要服务于什么
  • 7.1 帧 pacing:VKEXTpresenttiming 与被忽略的第三条时间线
  • 7.2 一个把带宽与帧率连接起来的简单估算
  • 八、工程落地:跨厂商渲染图决策规则
  • 规则一:把 pass 合并决策建立在 tile 内存工作集之上,而不是"能合就合"
  • 规则二:对每个关键 render pass 启用 merge feedback 断言,而不是假设合并发生
  • 规则三:区分"目标是吞吐"与"目标是延迟",并据此决定 pass 粒度
  • 规则四:在 Mali-G710 之前的硬件上,不要用多 attachment tile 依赖链做延迟光照
  • 规则五:把 transient attachment 的声明做成渲染图的一等公民
  • 规则六:不要把 tile 内存访问当作比纹理访问更快的东西来规划
  • 规则七:在 Adreno 上,把 FlexRender 的模式不确定性当作渲染图设计的约束
  • 规则八:不要阻止 Adreno 的 Concurrent Binning
  • 规则九:把带宽优化的验收标准写进测试,而不只是写进设计文档
  • 规则十:frame pacing 的测量要覆盖第三条时间线
  • 九、讨论、局限与给工程师的检查清单
  • 9.1 这套结论的适用边界
  • 9.2 一个反直觉的总结
  • 9.3 发布前检查清单
  • 参考文献

移动端 TBDR 渲染架构工程 2026:subpass 合并的三重代价

把桌面端直推渲染的直觉搬到手机上,移动端的 tile-based 延迟渲染会先让你赢带宽、再让你输时钟,本文拆解这笔交易的三个隐藏代价与可落地的跨厂商决策规则。

2026年10月23日·约 65 分钟阅读·19,259 字·10 次阅读·博主
#游戏引擎与渲染
移动端 TBDR 渲染架构工程 2026:subpass 合并的三重代价

Index

  • 一、问题的提出:从桌面直推到手机 tile 的直觉崩塌
  • 二、形式化:TBDR 的两阶段模型与带宽算术
  • 三、Mali:subpass 合并的三重代价
  • 3.1 代价一:GPU 队列调度 —— binning 被强制串行化
  • 3.2 代价二:warp 调度 —— 依赖跟踪器的粒度决定 false dependency 密度
  • 3.3 代价三:功能单元吞吐与缓存 —— 把纹理换成 tile 反而更慢
  • 四、Adreno:FlexRender、Concurrent Binning 与 GMEM 视角
  • 4.1 FlexRender:当 tile-based 假设不成立时的自动退出
  • 4.2 Concurrent Binning:把 binning 从关键路径上摘下来
  • 4.3 GMEM 里的 Compute 与 per-tile draws
  • 五、可观测性:判断 subpass 是否真的合并了
  • 六、Vulkan 1.4 迁移:dynamic rendering local read 的时代
  • 七、帧调度、功耗与热:带宽优化最终要服务于什么
  • 7.1 帧 pacing:VKEXTpresenttiming 与被忽略的第三条时间线
  • 7.2 一个把带宽与帧率连接起来的简单估算
  • 八、工程落地:跨厂商渲染图决策规则
  • 规则一:把 pass 合并决策建立在 tile 内存工作集之上,而不是"能合就合"
  • 规则二:对每个关键 render pass 启用 merge feedback 断言,而不是假设合并发生
  • 规则三:区分"目标是吞吐"与"目标是延迟",并据此决定 pass 粒度
  • 规则四:在 Mali-G710 之前的硬件上,不要用多 attachment tile 依赖链做延迟光照
  • 规则五:把 transient attachment 的声明做成渲染图的一等公民
  • 规则六:不要把 tile 内存访问当作比纹理访问更快的东西来规划
  • 规则七:在 Adreno 上,把 FlexRender 的模式不确定性当作渲染图设计的约束
  • 规则八:不要阻止 Adreno 的 Concurrent Binning
  • 规则九:把带宽优化的验收标准写进测试,而不只是写进设计文档
  • 规则十:frame pacing 的测量要覆盖第三条时间线
  • 九、讨论、局限与给工程师的检查清单
  • 9.1 这套结论的适用边界
  • 9.2 一个反直觉的总结
  • 9.3 发布前检查清单
  • 参考文献

一句话摘要:把桌面端直推渲染的直觉搬到手机上,移动端的 tile-based 延迟渲染会先让你赢带宽、再让你输时钟,本文拆解这笔交易的三个隐藏代价与可落地的跨厂商决策规则。

移动端 TBDR 渲染架构工程 2026:subpass 合并的三重代价

一、问题的提出:从桌面直推到手机 tile 的直觉崩塌

过去十年,图形程序员建立了一整套几乎成为肌肉记忆的渲染直觉:延迟渲染有独立的 G-buffer pass,屏幕空间 pass 读上一 pass 的输出纹理,draw call 越多越要批量化,texture fetch 吞吐比 ALU 更值得优化,渲染图(render graph)的目标是最小化带宽。这套直觉的土壤是桌面级立即模式 GPU——NVIDIA、AMD、Intel 的独显本质上是立即模式渲染器(immediate-mode renderer):光栅化器扫过屏幕,任何一次纹理采样都走一条通往显存颗粒的路径,延迟渲染把中间结果写回显存再读出,代价可以换算成 GB/s,而 PC 平台的带宽通常宽裕到足以容忍这笔开销。

手机不是这样的世界。移动端 SoC 里的 GPU 绝大多数是基于 tile 的延迟渲染器(tile-based deferred renderer, TBDR)——Arm Mali 系列、高通 Adreno 系列、Imagination PowerVR 系列,以及苹果自研的 Apple GPU,都属于这一类。它们共享同一个核心结构:GPU 内有一块容量有限但极快的片上 tile 内存(Mali 叫它 tile memory 或 on-chip SRAM,Adreno 叫它 GMEM),整幅屏幕被切成若干个小 tile,每个 tile 的完整生命周期——binning、几何处理、片元着色、深度测试、混合——全部在 tile 内存里跑完,最后一次性写回系统内存。

Arm 的官方文档把这件事讲得很直白:基于 tile 的渲染方式被设计用来最小化片元着色期间对外部内存的访问次数,做法是把屏幕切成小块,每个小块着色完成后才写出到内存;为了做到这一点,GPU 必须提前知道哪些几何体覆盖哪些 tile,因此把每个 render pass 拆成两个处理阶段。据 Arm 文档记载,Mali GPU 渲染的是 16x16 像素的 tile。

这段描述背后有一个对渲染架构影响极其深远、却长期被桌面端从业者低估的推论:在 TBDR 上,"一次 render pass 边界"不只是引擎作者的一次逻辑分组,它极有可能是物理上的一次 tile 内存写回。延迟渲染的经典形态是 geometry pass → lighting pass;在这两个 pass 之间交换 G-buffer,逻辑上你可以写成"一次 render pass 里的两个 subpass",物理上则可能完全不落地内存。但在很多实现里,驱动会把它们当成两个独立的 render pass,于是那张可能是 128 bit/px 或更高的 G-buffer 被完整写出、完整读回。

Arm 的开发者博客在 2025 年 11 月 6 日发布的一篇文章里量化了这笔开销:传统延迟光照渲染使用 two render passes,第一个 render pass 用 multiple-render-target geometry buffer 收集每个片元的材质数据,第二个 render pass 计算光照,把 G-buffer 纹理作为光照方程的主要输入。如果不使用 subpass,这涉及把 G-buffer 写回内存后在第一个 render pass 中再次读回;一个 G-buffer 每像素可能消耗 128 bit 颜色数据、有时更多,这次往返主内存消耗大量内存带宽,而访问 DRAM 是耗能密集的。改成 subpass 就能让 G-buffer 留在 tile 内存里,用完即弃。

听起来这是一个纯粹的免费午餐。但同一篇文章的标题是《Vulkan subpasses: the good, the bad, and the ugly》——而如果你读完 Arm 官方给出的性能判词,会发现它比标题暗示的还要尖锐:subpass 不只是"可能不提升性能",它可能让时钟性能更差。这与桌面端"延迟渲染虽然费带宽但通常更快"的常识正好相反。

这就是本文要解决的问题。移动端 TBDR 的 subpass 优化不是一句"用 subpass 省带宽"的口号,而是一笔用时钟性能换带宽与能耗的交易。交易的三重代价分别是调度粒度、依赖跟踪、功能单元吞吐——它们在硬件手册里看不见,只在你把 tile 内存路径真正打开之后才会在 GPU 帧时间上显形。本文的目标是把这三重代价拆开、给出可观测的判读方法,并落成一套跨厂商(Adreno 与 Mali 硬件行为并不一致)可执行的渲染图决策规则。

在展开之前,先把一个常见的时代错位说清楚:这类知识近两年已经发生了实质性的硬件代际变化。文中所有关于具体硬件代际(tile 尺寸、依赖跟踪器数量、纹理与 tile 访问的吞吐比)的陈述,我会在每一处标明来源与时间。如果你读到的是几年后的文章,请把具体的代际数字当作"演化轨迹的样本"而不是"当前规格"。

二、形式化:TBDR 的两阶段模型与带宽算术

要把这笔交易算清楚,先把 TBDR 的执行模型写成可分析的式子。

Arm 文档给出的两阶段伪代码是理解一切的起点:

# Pass one
for draw in renderPass:
    for primitive in draw:
        for vertex in primitive:
            execute_vertex_shader(vertex)
        if primitive not culled:
            append_tile_list(primitive)

# Pass two
for tile in renderPass:
    for primitive in tile:
        for fragment in primitive:
            execute_fragment_shader(fragment)

第一阶段跑完所有几何处理,生成一张 tile list(Adreno 称之为 visibility stream,Mali 称之为 bin list),指示哪些图元贡献给哪些屏幕 tile;第二阶段逐 tile 执行所有片元处理,tile 完成时写回内存。这个伪代码虽然是教学用的,但它的两个副产品——tile list 的产出成本与tile 的写回时机——恰好就是后面三重代价的物理来源。

设屏幕分辨率为 W×HW \times HW×H,tile 边长为 TTT,则 tile 总数为:

Ntile=W×HT2N_{tile} = \frac{W \times H}{T^2}Ntile​=T2W×H​

以 1080p(W=1920W=1920W=1920, H=1080H=1080H=1080)和 Arm 文档记载的 T=16T=16T=16 为例,Ntile=1920×1080/256=8100N_{tile} = 1920 \times 1080 / 256 = 8100Ntile​=1920×1080/256=8100 个 tile。这是相当多的 tile,每个 tile 都要经历一次着色启动(shader launch / warp 填充)与一次写回事务的组织。

现在定义一个 render pass 边界的带宽代价。设某 attachment 在 pass 边界处需要落地,其每像素字节数为 bbb 字节,pass 结束后其中被保留(storeOp=STORE)的比例为 rrr,则该边界引入的写回带宽为:

Bwrite=W×H×b×rB_{write} = W \times H \times b \times rBwrite​=W×H×b×r

对应的读回带宽(下一 pass 需要 loadOp=LOAD)在数值上同阶。两端相加,一个 G-buffer 边界的一次往返大约是 2×W×H×b2 \times W \times H \times b2×W×H×b。代入 1920×1080×161920 \times 1080 \times 161920×1080×16 字节(128 bit)与 r=1r=1r=1,单次写回就是 33.2 MB,往返 66.4 MB。在 60fps 的 16.67ms 预算里,一个 pass 边界引入 66.4 MB 往返、而 GPU 只需为它花掉约 0.5ms 的纯搬移时间的情况下,若平台可用的持续显存带宽是 20 GB/s 量级(一个保守的中端估算,实际中高端 SoC 的 LPDDR 带宽更高但共享给 CPU/GPU/NPU/ISP),那么 66.4 MB 就吃掉约 3.3 ms——接近整个帧预算的五分之一,且这部分时间与场景复杂度完全无关,是纯粹的结构性开销。这就是 subpass 值得存在的原因。

但现在把 tile 内存这一侧也算进来,这是交易的关键。TBDR 的 tile 内存必须容纳单个 tile 内所有活跃 attachment 的完整工作集。对于一个 tile,需要:

Mtile≥T2×(∑ibi⋅si)+MoverheadM_{tile} \ge T^2 \times \left(\sum_{i} b_i \cdot s_i\right) + M_{overhead}Mtile​≥T2×(∑i​bi​⋅si​)+Moverhead​

其中 bib_ibi​ 是第 iii 个 attachment 的每像素字节数,sis_isi​ 是 MSAA 采样倍数(颜色通常需要 sis_isi​ 倍,深度视实现而定),MoverheadM_{overhead}Moverhead​ 是 tile list、深度压缩元数据等辅助结构。

代入 T=16T=16T=16、8 个 16-bit attachment、MSAA 4x:Mtile≥256×(8×2×4)=16384M_{tile} \ge 256 \times (8 \times 2 \times 4) = 16384Mtile​≥256×(8×2×4)=16384 字节 = 16 KB 已经是相当紧的估计(实际还要加上深度、stencil 与更保守的采样假设)。这个约束直接解释了 Arm 文档里关于 AFBC 与丢弃行为的说明,也解释了为什么"attachment 数量"在 TBDR 上是一个架构级预算而非风格偏好——它是 tile 内存容量的直接消费者。

到这里带宽侧的收益已经清楚了。但注意上面两个式子里没有任何一个变量对应"时间"。第 1 节的式子告诉你省了多少字节,它完全没有告诉你为了少写这 66.4 MB,GPU 多花了多少纳秒去填充那 8100 个 tile 的 shader、把 binning 串行化、或者在依赖跟踪上等待。这就是 Arm 那篇文章标题里 "the bad" 与 "the ugly" 的入口。

三、Mali:subpass 合并的三重代价

这一节逐条拆解 Arm 官方列出的性能影响。原文的判词值得原样引用,因为它比大多数二手教程更诚实:subpass 可以减少内存带宽,但值得注意的是它们并不必然改善时钟对时钟的性能。对 Arm GPU 而言,峰值纹理性能总是至少与 tile 内存访问的性能一样快;把纹理采样换成 tile 读取,本身不带来直接性能收益,除非你撞上了系统带宽上限或把纹理缓存冲刷掉了。用 subpass 的好结果标准是:性能保持不变,但内存带宽显著下降。

"保持不变"就是这里的上限目标,不是"变快"。Arm 给出的区间判断是:subpass 可能比不用 subpass 在时钟对时钟性能上更慢,因为一个带合并 subpass 的 render pass 对 GPU 调度来说是一个更难的工作负载。

3.1 代价一:GPU 队列调度 —— binning 被强制串行化

Arm 列出的第一个性能影响来自合并 render pass 所隐含的更粗的调度粒度。Mali 是 tile-based 的,必须在开始片元着色之前完成整个硬件 render pass 的几何 binning。合并 subpass 增加了初始的几何工作量,并推迟了片元着色的开始时刻。根据应用 GPU 其他工作的时序与调度,这部分增加的延迟可能无法被隐藏。

这句话的工程含义比它字面上更重。在立即模式 GPU 上,你可以把 geometry pass 的 binning 与前一个 pass 的片元着色重叠——因为 GPU 是流水线式的,上一个 pass 还在着色时下一个 pass 的 binning 已经在另一个流式单元里跑了。但在 TBDR 上,一个硬件 render pass 内部 binning 与 main phase 是串行的。Arm 明确对比了这一结构差异:分离的 render pass 可以在 render pass 之间对 binning 与 main phase 工作做流水线化,但 subpass 不行,必须串行跑它们。

后果是:如果你的渲染图有很多小 pass(典型症状:UI 叠加、少量后处理、全屏 blit),把 pass 边界从"合并进一个 render pass"改成"各自独立 render pass",反而可能获得更好的重叠——你的 latency 会低,帧时间可能持平。

Arm 提供的部分缓解路径是硬件代际演进:从 Immortalis-G920 系列开始的近期 GPU 改善了初始顶点着色与 tile binning 工作负载的性能;应用可见的串行 binning 阶段带来的影响在随时间减小,并预期未来硬件世代会有进一步改善。所以这条代价的强度是依赖 GPU 代际的——在 G920 系列之后的 Mali 上,这条顾虑需要重新实测,不能沿用老设备的结论。

3.2 代价二:warp 调度 —— 依赖跟踪器的粒度决定 false dependency 密度

第二个性能影响来自 tile 处理期间 shader core 内部的调度依赖。一个读取 tile 内存值的片元着色器,必须等待更早的层写入该值,并在此之前一直 stall。

关键在于 Mali shader core 的依赖跟踪器粒度。在 Mali-G710 系列之前,shader core 每个像素只有一个依赖跟踪器。片元在完成其所有 tile 内存访问之前无法访问 tile 内存。结果是悲观的调度以及多 attachment 使用下的 false dependency:GPU 可能因为大量 warp 阻塞在等待更早的层完成而耗尽可运行的 warp。

Arm 描述的三层调度示例很有说服力:三层各自访问三个 tile 内存位置,红色条显示 shader 尝试读写 tile 内存中的 G-buffer 位置,每一层在它的第一次访问尝试上就 stall,阻塞直到它成为最老的层。这个"阻塞直到成为最老层"的模式在长依赖链上会级联放大——层数越多,级联越长。

而从 Mali-G710 系列起,Mali shader core 具有每像素多个 framebuffer 依赖跟踪器,这允许依赖在 shader 推进过程中增量释放。在同一三层的对比示例中,没有任何一层 stall:当一层到达 tile 内存中某个具体位置的同步点时,前一层已经完成了释放该依赖的操作。

同样的代际跃迁还发生在 tile 尺寸上:自 Mali-G710 系列起,Mali shader core 也切换到更大的 32x32 像素 tile。这减少了基于层的 stall,因为 shader core 可以通过在更多唯一像素位置上运行 warp 来填充,减少对带依赖的空间重叠 warp 的依赖。

把两件事合起来看,可以得到一个明确的设计结论:移动端延迟光照的 subpass 优化,在 Mali-G710 之前的老架构上有一个无法用代码绕开的硬性天花板。 你不能通过把 shader 写得更聪明来修复一个"每像素单个依赖跟踪器"的硬件限制。你只能接受更长的级联 stall,或者——换用不需要多 attachment tile 依赖链的算法(比如 Clustered Forward 直接在片元里算光照)。这是一个硬件代际决定的算法选择,而不是一个可以用 profiling 调优的 shader 问题。

3.3 代价三:功能单元吞吐与缓存 —— 把纹理换成 tile 反而更慢

第三个也是最反直觉的成本项:tile 内存读取使用的是与纹理读取不同的功能单元。自 Mali-G77 系列(Valhall 架构家族的第一个)起,纹理采样速度是 tile 内存访问的两倍。Arm 对此的措辞是:如果某个 render pass 已经重度负载了 tile 访问,那么改成使用合并 subpass 可能比读取纹理更慢——这在真实内容中不常见,但如果你的 shader 非常简单,这是需要意识到的事。

这条的单点代价是 2 倍(tile 访问只有纹理访问一半吞吐),但它的杀伤力在于它会与 3.1、3.2 相乘。设想一个光照 shader,它有 60% 的 ALU 与 40% 的内存操作,其中原本 30% 是纹理采样、10% 是 tile 访问:合并 subpass 后 tile 访问变成 40%,tile 相关的执行时间增加 4 倍,同时 binning 被串行化,同时依赖链变长。三者叠加的结果可以轻易超过 subpass 换来的带宽收益。

Arm 还给出了第四个层面的影响:shader core 缓存。运行合并 subpass 时,每个 tile 必须循环访问更多唯一资源——shader 代码、buffer、纹理——来完成处理。每个 tile 中增加的唯一资源数会对 shader core 缓存施加压力。如果在高资源压力下发生缓存抖动,这可能降低性能。

以及第五个,影响的是优化器而非硬件:shader core 优化。Mali GPU 包含在适当时跳过不必要工作的优化。empty tile elimination 优化可以跳过没有几何体的 tile,包括 render pass 的 loadOp=LOAD 回读行为;transaction elimination 优化可以跳过对某个 tile 的 COLOR0 attachment 写入,即使 storeOp 是 STORE。subpass 会改变这两个优化的触发方式。Arm 给的例子是:没有 subpass 的情形下,两个串行的 render pass,render pass 0 的几何体只部分覆盖屏幕,render pass 1 渲染一个全屏 quad 读取 render pass 0 的输出作为纹理;render pass 0 的颜色与深度用 loadOp=LOAD,两个 render pass 各自写各自的 COLOR0——此时 transaction elimination 与 empty tile elimination 都能高效工作。合并成 subpass 后,tile 需要同时服务两个阶段的工作集,优化的命中条件被破坏。

这五个层面合起来,构成对移动端渲染架构的第一个可执行结论:subpass 合并的收益函数不是单调的,它取决于你的 tile 内存工作集大小、attachment 数量、shader 的纹理/tile 访问比、binning 负载,以及你的目标究竟是吞吐还是延迟。 盲目合并可能在一个 60fps 的目标下把帧时间推高,而省下的带宽根本用不掉。

四、Adreno:FlexRender、Concurrent Binning 与 GMEM 视角

如果 Mali 的教训是"合并有代价",那么 Adreno 的架构提供一个更激进的答案:把 tile-based 这个前提本身变成可选项。高通在其开发者文档(80-78185-2,更新于 2026 年 9 月)中这样描述 Adreno 的 tile-based 架构:为优化低功耗与受内存带宽限制的设备上的渲染,Adreno GPU 使用基于 tile 的渲染架构,这个渲染机制把场景 frame buffer 打断成小的矩形区域。区域尺寸自动确定(尽管开发者可以影响 bin 的数量,bin 数量也定义了 bin 尺寸),以便使用 GPU 上本地的低延迟内存(称为 GMEM),而不是用带宽受限的总线访问系统内存。

文档随后给出与 Mali 相同的两阶段结构(binning 产出 visibility stream,rendering pass 逐 bin 处理),但接下来的三个特性是 Adreno 独有的工程变量。

4.1 FlexRender:当 tile-based 假设不成立时的自动退出

FlexRender 允许 Adreno GPU 在基于 tile 的 binned rendering 与直接渲染到 frame buffer 之间切换——因为取决于工作负载,直接渲染或 binned 渲染可能提供更优性能。驱动和 GPU 分析给定 render target 的渲染参数并自动选择模式。

对开发者而言,决定走哪条路的驱动启发式并未暴露。这一条极其重要:如果模式选择不可见且不可控,那么"subpass 是否合并"在 Adreno 上就不是一个纯应用层决策,而会被驱动的启发式二次决策。你的渲染图设计(pass 粒度、attachment 布局、屏幕覆盖比例)只是影响这个决策的输入之一,而不是决定它。

对工程实践的含义是:在 Adreno 设备上做 subpass A/B 测试时,必须用硬件计数器验证实际走的是哪条路径,而不是假设 API 层面写了 subpass 硬件就合并了。这一点在第五节展开。

4.2 Concurrent Binning:把 binning 从关键路径上摘下来

Concurrent binning 由 Snapdragon A7x 系列引入。对每个 render pass,如果没有依赖阻止它,concurrent binning 允许 binning pass 在 vertex shader 执行之前异步运行。

与同步 binning 相同,编译器会生成一个 position-only vertex shader——编译器试图把它简化为只影响顶点位置的指令的顶点着色器版本——用来决定每个三角形放进哪个 bin(若三角形跨越多个 bin,则放入多个)。区别在于并发 binning 时 position-only vertex shader 的执行与其他操作异步进行。

激活它不需要额外步骤,但需要注意不要阻止它。 这一条比前两条更微妙:Concurrent Binning 是默认行为,而阻止它的方式是特定的依赖模式与高 GPR 使用率。Qualcomm 文档提到驱动会在 GPR 使用率低时尝试启动并发 binning,即使存在顺序依赖——这意味着触发条件是负载相关的,你不能静态决定。

profiling 侧有一个实用提示:Snapdragon Profiler 在抓取 trace 时需要包含 drawcall stages 属性;并发 binning 信息会显示在 Render Pipe 之下作为 Binning Pipe(若处于活动状态)。而同步 binning 位于 Render Pipe 上。这条信息可以直接用来在 trace 里区分两者,不必靠猜测。

4.3 GMEM 里的 Compute 与 per-tile draws

高通文档另有两处与 TBDR 直接相关的扩展,都围绕"如何更好地利用 tile 内存":

VK_QCOM_tile_memory_heap 允许你在 GMEM 中分配 image 与 buffer,并指定它们何时跨 render pass 持久、何时不持久(前提是它们不超出某些因设备而异的限制,image 与 buffer 各有特殊限制)。该扩展的收益表述为:通过用相对耗电的内存总线流量替换为更廉价的 GMEM 操作来降低功耗,并提升执行速度(因为 GMEM 显著快于系统内存)。

VK_QCOM_tile_shading 允许你做两件事:在 renderpass(或 dynamic rendering scope)内使用 compute shader 并访问存储在 GMEM 中的 tiled render attachment——于是 compute shader 可以高效地与 fragment shader 在 GMEM 中协作,不需要昂贵的 barrier 或不必要的系统内存占用;以及以 GPU-Driven 方式记录并派发命令,使用 per-tile draws。

per-tile block 的语义对引擎设计有直接影响。文档说明一个 per-tile block 是被 vkCmdBeginPerTileExecutionQCOM 与 vkCmdEndPerTileExecutionQCOM 括起来的一段命令序列,VK_QCOM_tile_shading 在该括号内的每条命令上都生效,这意味着:tile-based 延迟渲染会被尽可能多地采用,因此 Snapdragon Profiler 应当对采用 VK_QCOM_tile_shading 的 renderpass 对应的 Surface 显示 "HwVizBinning" Render Mode;命令的执行顺序不变,与典型的 Adreno tile-based rendering 在这方面一致;以及 draws(称为 "per-tile draws")与 dispatches 保证每个 tile 执行一次且不被丢弃(但没有任何排序执行循环或其他异常控制流)。

最后这条是本文最想在引擎侧标出来的一条:"每 tile 执行一次"的语义意味着一个 per-tile draw 本质上是一个 tile 级的循环体。把它和 3.1 节的"合并 subpass 导致 binning 与 main phase 串行"放在一起看,你就得到一个很有张力的图景——per-tile draws 让 tile 级的显式编程成为可能,但显式编程的代价是 GPU 失去了把不同 tile 的 binning/着色相互重叠的自由度。这两者是一个硬币的两面。

五、可观测性:判断 subpass 是否真的合并了

前面所有的结论都指向同一个前置问题:你怎么知道 subpass 在目标设备上真的被合并了? 因为如果没合并,你在优化一个不存在的东西,而实测的结果可能只是让你相信"subpass 帮不上忙",从而错误地放弃正确的优化方向。

Arm 博客的 FAQ 给出了一个直接答案:在 API 层面,你可以使用 VK_EXT_subpass_merge_feedback 扩展查询当一个 render pass 被创建时的合并情况。这让你能在预期合并发生时确认预期的合并确实发生了。该扩展在 r38p0 驱动中首发,现在已广泛可用。

这条路径的价值在于它把验证从 profiling 前移到了 render pass 创建时——一个明确的、可程序化的返回值,比事后看 trace 里的时间条更可靠。这是一个可以直接写进引擎断言的检查:在开发版本里,对关键 render pass 查询 merge feedback,如果驱动报告未合并而你本该依赖合并,打日志而不是静默降级。

在 Arm Streamline profiler(Arm Performance Studio 的一部分)中,Arm 文档指出两种方式可以直接或间接看到 subpass 合并的效果:

  • 直接测量:Mali timeline 调度 trace 会显示已执行的硬件 render pass,你将能够看到为你提交的每个 API render pass 调度了多少个硬件 render pass。这是一个相对较新的特性,需要 CSF GPU 和 r51p0 或更新的驱动,不过部分搭载 r49p1 驱动的设备已作为技术预览包含了它。
  • 间接测量:Mali 性能计数器可以测量硬件 render pass 写入的像素数。使用合并 subpass 时,你应当看到相比使用分离 render pass 的构建像素计数下降。

第二条在工程上尤其值得强调,因为它给出的信号是带宽方向的:如果合并发生了、写回像素数下降了,但帧时间没变,那正好对应 Arm 说的"好的结果是性能保持不变,但内存带宽显著下降"。这时你不应该继续调 subpass,而是应该把精力转移到真正限制帧时间的地方去。 这个判读纪律能避免大量无效的 profiling 循环——很多团队在带宽下降后仍然继续优化带宽,因为"带宽掉了"看起来像个胜利。

Qualcomm 侧的对应信号是 Render Mode。上面提到,采用 VK_QCOM_tile_shading 的 renderpass 对应的 Surface 应当在 Snapdragon Profiler 中显示 "HwVizBinning"。而 FlexRender 决定了 Adreno 是在 binned 还是 direct 模式下工作,这意味着同一个渲染图在 Adreno 上可能实际执行在 direct 模式下——此时你在 Mali 上验证的 tile 内存假设在 Adreno 上完全不成立。Qualcomm 文档有一节专门列举触发 direct 模式的场景,值得作为 Adreno 上"渲染图退化"的排查入口。

六、Vulkan 1.4 迁移:dynamic rendering local read 的时代

前面所有的 subpass 讨论都建立在一个前提上:你用的是传统的 VkRenderPass / VkFramebuffer / subpass 模型。但 Vulkan 的演进正在把这件事的优先级往另一个方向推,这也是移动端渲染架构在 2026 年必须面对的迁移。

Arm 文档在 FAQ 里给出了一条明确的推荐:VK_EXT_shader_tile_image 扩展比 VK_KHR_dynamic_rendering_local_read 更早可用,因此你可以在更多设备上使用它。不过,当 dynamic rendering local read 可用时,我们推荐使用它,因为它现在是核心功能并且跨厂商可用。

这句话包含两个信息,且两个都很重要。第一,dynamic rendering local read 已经在 Vulkan 1.4 中成为核心功能。第二,迁移会带来覆盖率问题——core 功能不代表旧设备支持,在 Android 这类设备碎片化严重的平台上,覆盖率是这个选择的实际门槛。

Arm 给出的理由是跨厂商可用性,这正是核心功能的价值:subpass 语义本身是 Vulkan 标准的一部分,但 tile 内存能否保留是实现行为;而 dynamic rendering local read 是你在 tile-based 硬件上表达"我要读写同一 pass 内的 attachment"的标准化方式,不依赖老的 VkRenderPass 对象模型。

这里有一个值得引擎作者认真对待的判断。移动端渲染架构正面临一个反直觉的方向:桌面端在往 dynamic rendering(去掉 render pass 对象)迁移,而移动端因为 tile 内存的需求,反而更依赖 pass 内的 attachment 局部读写能力。 两条演进路线在 local read 这个能力上交汇。Vulkan 1.4 把这个能力放进核心,意味着"一个 pass 内做多次子阶段、每次之间读写同一批 tile attachment"这个模式,有了不依赖厂商扩展的跨平台表达方式。

对引擎架构的具体影响是:如果你维护的是一个渲染图系统,把它从"显式 render pass 对象"迁移到 "dynamic rendering + local read" 时,pass 内部的阶段划分(lighting pass 读取 geometry pass 的输出)需要用 local read 而不是 subpass input attachment 来表达。这两个表达方式在 API 表面相似,但语义和覆盖率不同。迁移决策不应该只看"Vulkan 1.4 有这个功能",还要看目标设备的 apiVersion 与 dynamicRenderingLocalRead 属性支持——这正是第五节那种"能力探测而非假设"纪律的又一个应用场景。

需要克制的地方:本文不对具体引擎(Unity / Unreal / Godot)在各版本中默认使用哪种渲染 API 模式做任何陈述。据我截至 2026 年 10 月的观察,主流引擎对 Vulkan dynamic rendering 与 local read 的采纳节奏各不相同,具体默认值应以你所用的引擎版本的官方文档为准;未见有统一的公开数据可以支持"某引擎在某平台默认启用某模式"这类概括。

七、帧调度、功耗与热:带宽优化最终要服务于什么

带宽优化的收益最终要落到某个物理量上。在移动端,这个物理量不是帧时间的绝对值,而是在热约束下能维持的帧率,以及这个帧率对应的用户体验时长。Qualcomm 文档在 VRS 一节给出的表述把这个权衡说得很清楚:移动设备上性能与功耗紧密耦合,使用更粗的着色率不仅能提升性能,还能改善功耗与游戏的热表现,最终增加用户的游玩时间。

这个"最终增加用户的游玩时间"的措辞值得展开。移动端 GPU 优化的目标函数与 PC 有一个结构性差别:

  • PC 上,帧时间的改善立刻转化为帧率提升,用户能感知到"更流畅"。
  • 移动端上,帧时间改善首先转化为更低的每帧能耗,进而转化为更低的芯片温度与更高的可持续频率上限。这会在长时间游戏的后半程兑现为更稳定的帧率,而这个差异往往在最初几分钟的短测中完全看不出来。

这解释了为什么第三节里 Arm 那句"好的结果标准是性能保持不变,但内存带宽显著下降"在移动端是合格甚至优秀的工程结果,而在桌面端会被视为失败。在移动端,减少 DRAM 访问的收益不在于那 0.5ms 的搬移时间,而在于它降低了 SoC 的热负载,从而让 GPU 能在更长时间内维持在 boost 频率上。这是一个关于稳态性能而非峰值性能的优化。

7.1 帧 pacing:VK_EXT_present_timing 与被忽略的第三条时间线

带宽与功耗的优化最终要落到帧的呈现节奏上。而帧节奏这件事在过去长期是移动端渲染工程中被忽视的环节。

Khronos 在 2025 年 12 月 4 日发布的博客《VK_EXT_present_timing: the Journey to State-of-the-Art Frame Pacing in Vulkan》中,给出了一个技术史视角的诊断。作者 Lionel Duc(NVIDIA 的 Vulkan 图形驱动工程师)指出:现代图形 API 引入了 swapchain 概念,虚化了显示 frame buffer 轮换的细节,但swapchain 设计仍然由吞吐量驱动——最大化每秒帧数。Vulkan 也不例外:VK_KHR_swapchain 允许优秀的呈现流水线化,但没有专门用于 frame pacing 的 API。换句话说,把帧排进队列很容易,但几乎不可能知道那些帧何时真正出现在屏幕上。

历史路径也很清楚:VK_GOOGLE_display_timing 是第一次尝试,但呈现灵活性不足;VK_KHR_present_wait 为 frame pacing 提供了帮助,但没有达到显式基于时间呈现的精度。最终是 VK_EXT_present_timing——而且它现在才成为 Vulkan 的正式新增功能。

这个扩展的机制包含两个可独立暴露的基础能力:接收关于先前呈现请求的时序反馈,以及为每个请求显式指定目标呈现时间。是这两者的组合使应用能够实现平滑一致的动画。

其中一个诊断特别值得移动端工程师注意,因为它描述的是一个非常常见的错误:许多图形程序员以独立的 CPU 与 GPU 时间线思考,但常常忘记第三条:真正显示图像的硬件。开发者最终希望用户看到的物体移动速率,恰好等于内部模拟它的速率。要做到这一点,应用需要知道下一张图像何时被显示,才能把动画步进对准那个时刻。

而移动端对低延迟的诉求又增加了另一重张力。历史上游戏社区的解法是关闭垂直同步并容忍必然的撕裂,以获得最低可能延迟;Vulkan 提供了 VK_PRESENT_MODE_MAILBOX_KHR 用于无撕裂的低延迟显示,但它的可选状态与有限采用意味着开发者无法依赖它。此外,开发者控制之外的因素——软件 overlay 与桌面合成器——会影响帧时序。在 Android 上,系统级 overlay 与合成器的存在意味着"你无法完全控制呈现时刻"。

移动端有一个桌面端基本没有的特殊约束:Android 的 frame pacing 常常与 Android Choreographer 及 SurfaceFlinger 的合成策略深度耦合。具体到实现,Choreographer 的 vsync 信号、SurfaceFlinger 的 buffer 队列深度、以及厂商对 display pipeline 的调校共同决定了实际的呈现节奏。截至 2026 年 10 月,Vulkan 侧的 VK_EXT_present_timing 支持状态仍是进行中的——NVIDIA 开发者驱动已可用,Mesa 实现即将可用,Android 17 的早期开发者与 beta 版本跟进中。EXT 前缀意味着并非所有平台都支持该扩展。这条时间线清楚地说明:当你在移动端调帧 pacing 时,可用的标准化工具仍在成熟过程中,短期内必须依赖平台特定机制并自己测量。

7.2 一个把带宽与帧率连接起来的简单估算

把上面两节合起来,可以得到一个粗略但有用的稳态估算框架。设单帧的总 DRAM 读写量为 DDD 字节(这个数字可以通过第三节的式子对每个 pass 边界累加得到),平台持续带宽为 WdramW_{dram}Wdram​,则纯搬移时间的下界是:

tmem=DWdramt_{mem} = \frac{D}{W_{dram}}tmem​=Wdram​D​

若不同时长存在同时着色竞争,则 tmemt_{mem}tmem​ 与 tshadert_{shader}tshader​ 之间可能部分重叠;但关键是当 tmemt_{mem}tmem​ 与 tshadert_{shader}tshader​ 序列性地争抢同一份时间预算时(例如在带宽受限的 SoC 上),总帧时间的下界是 max⁡(tshader,tmem)\max(t_{shader}, t_{mem})max(tshader​,tmem​) 而非 tshader+tmemt_{shader} + t_{mem}tshader​+tmem​——只有在超出带宽时才被带宽约束。

这个式子的工程价值是它给出了优化的判据:如果 tmem<tshadert_{mem} < t_{shader}tmem​<tshader​ 且有余量,那么继续做带宽优化对帧时间没有直接收益(对应第五节说的"像素计数下降但帧时间没变")。此时正确的下一步是把注意力转向调度重叠(第三节的 3.1)与算法选择(第三节的 3.2 结论)。而如果 tmem>tshadert_{mem} > t_{shader}tmem​>tshader​,则你的瓶颈是带宽,此时减少 pass 边界的写回是最直接的手段——而 subpass 正是为这一刻准备的。

需要说明的是,这个框架忽略了 tile 内存的容量约束(第二节的 MtileM_{tile}Mtile​)、缓存行为、以及驱动启发式,因此它是诊断的第一层筛选工具,而不是性能模型。用它排除掉"带宽不是瓶颈"的方向是可靠的,用它做精细预算则不够。

八、工程落地:跨厂商渲染图决策规则

现在把前面七节的结论压成可执行的东西。这一节的每一条规则都对应前文的一个具体发现,并且尽量写成可以在代码审查或渲染图配置里检查的形式。

规则一:把 pass 合并决策建立在 tile 内存工作集之上,而不是"能合就合"

第二节的 MtileM_{tile}Mtile​ 约束说明 attachment 数量在 TBDR 上是架构级预算。因此在渲染图里,每个 tile-based render pass 都应当有一个显式的 attachment 预算断言:把这个 pass 内所有 attachment 的 bi×sib_i \times s_ibi​×si​ 加总,超过 tile 内存容量的一个保守比例(建议留出至少 20% 余量给深度与元数据)时,在开发构建里报警或直接拒绝这个 pass 配置。这比在真机上偶发地"看起来有点慢"要早得多发现问题。

规则二:对每个关键 render pass 启用 merge feedback 断言,而不是假设合并发生

依据第五节:VK_EXT_subpass_merge_feedback 可以在 render pass 创建时给出合并结果。把它写成引擎层的开发期断言,同时把 Mali 的第二类信号(硬件 render pass 写回像素计数下降)作为 profiling 期的交叉验证。两个信号都失败的 pass 不应该继续依赖合并路径——它要么退回分离 render pass 路径,要么改为不依赖 tile 内存的算法。

规则三:区分"目标是吞吐"与"目标是延迟",并据此决定 pass 粒度

依据 3.1:分离 render pass 可以在 render pass 之间对 binning 与 main phase 做流水线化,而 subpass 必须串行。对固定帧率预算的游戏渲染(多数情况),带宽优先;对交互延迟敏感的场景(UI 密集、点击响应要求高的界面),分离 pass 可能更好。不要用一套 pass 粒度覆盖所有场景——至少把 UI 层与场景层分开配置。

规则四:在 Mali-G710 之前的硬件上,不要用多 attachment tile 依赖链做延迟光照

依据 3.2:G710 之前每像素只有一个依赖跟踪器,多 attachment 使用会产生悲观调度与 false dependency,GPU 可能耗尽可运行 warp。这不是 shader 层面的可调项。正确的应对是把算法换成 Clustered Forward(直接在片元里光照、不产生跨 pass 的多 attachment 依赖),而不是在老的 tile 依赖结构里反复调 shader。

规则五:把 transient attachment 的声明做成渲染图的一等公民

依据 Arm 文档:如果你告诉驱动某个 attachment 不需要被保留,驱动就不会把它写回主内存——在 OpenGL ES 2.0 是 glDiscardFramebufferEXT,OpenGL ES 3.0 是 glInvalidateFramebuffer,在 Vulkan 里则是恰当的 render pass storeOp 设置。这是在一个 pass 内部就能拿到带宽收益、且没有第三节那些合并代价的手段。它的风险低于 subpass 合并,收益在很多场景下已经足够。

规则六:不要把 tile 内存访问当作比纹理访问更快的东西来规划

依据 3.3:自 Mali-G77 系列(Valhall 第一个)起,纹理采样速度是 tile 内存访问的两倍。这条规则有点违反直觉——在 TBDR 上,tile 内存是"更快的本地存储",但它在计算吞吐维度上不占优;它占优的是避免往返。所以在写 shader 时,如果一个操作既可以从纹理读也可以从 tile 读,判据是"是否需要避免往返",而不是"哪个访问更快"。

规则七:在 Adreno 上,把 FlexRender 的模式不确定性当作渲染图设计的约束

依据 4.1:FlexRender 会自动切换 binned/direct,且启发式不暴露。这意味着渲染图里那些"依赖 tile 内存驻留"的优化在 Adreno 上可能完全不生效。因此依赖 tile 驻留的正确性前提(而不是性能优化)的逻辑,应当在 Adreno 路径上有等价的非 tile 正确性保证——也就是说,即使 tile 驻留没有发生,渲染结果也必须是正确的,不能有"这里只在 Mali 上是对的"的代码分支。

规则八:不要阻止 Adreno 的 Concurrent Binning

依据 4.2:Concurrent Binning 由 A7x 引入,默认生效,但依赖模式与高 GPR 使用率会阻止它。这意味着你的渲染图里的某些屏障与资源使用模式会静默地关掉一个延迟优化。在 Snapdragon Profiler 中用 drawcall stages 抓 trace,看 Binning Pipe 是否存在,是验证它是否被阻止的直接手段。

规则九:把带宽优化的验收标准写进测试,而不只是写进设计文档

依据第七节:移动端带宽收益兑现为热稳定后的帧率,而不是峰值帧率。所以验收应该在热稳态下进行——长时间连续运行后的帧率与温度,而不是冷启动的前几分钟。这一条在实践中极其容易被跳过,因为短测结果通常更好看。

规则十:frame pacing 的测量要覆盖第三条时间线

依据 7.1:显示硬件是独立于 CPU 与 GPU 的第三条时间线。所以"CPU 帧时间 8ms、GPU 帧时间 12ms"并不足以说明玩家感受到的流畅度;你需要知道帧实际上屏的时刻。由于 VK_EXT_present_timing 覆盖尚在进行中,这条规则在近期的实践形态是:在目标平台上建立自己的呈现时刻测量手段,并把 jitter(帧间呈现间隔的方差)作为一等指标。

九、讨论、局限与给工程师的检查清单

9.1 这套结论的适用边界

有几件事本文没有覆盖、也不该被读者推断出来:

第一,本文的所有硬件代际结论都带时间戳。 具体来说:16x16 与 32x32 的 tile 尺寸、"每像素一个到多个依赖跟踪器"、"纹理采样是 tile 访问两倍"这几条,都来自 Arm 在 2025 年 11 月的官方文档与开发者博客。Arm 在该博客中明确表示预期未来硬件世代会有进一步改善。因此这些数字应当被读作趋势的证据而不是当前规格。

第二,本文没有跨厂商的量化对比。 Arm 与高通在文档里给出的都是相对表述("带宽显著下降"、"快得多"),我没有找到(截至 2026 年 10 月)两家公开发布的、在同一基准下量化 subpass 合并收益与代价的对比数据。任何声称精确数字的第三方对比,在引用时都应先确认其方法论与硬件配置。

第三,本文不评价任何具体引擎或游戏的实现。 各引擎在移动端 Vulkan 上的渲染图组织、pass 粒度、以及是否使用 subpass,差异很大且随版本变化。以我截至 2026 年 10 月的观察,没有可据以概括的公开统一数据;请以你自己所用引擎版本的官方文档为准。

第四,第九节的检查清单是判断工具,不是配方。 它能帮你把"我该往哪个方向优化"这个问题问得更准,但不能替你回答"在这个具体场景里答案是什么"。后者只能靠在你自己的硬件上测量。

9.2 一个反直觉的总结

如果这篇文章只留一句话,我希望是这个:

在 TBDR 上,subpass 合并是一个带宽与能耗优化,而不是性能优化。

这句话的实践含义是——当你把它当作性能优化来做时,你会用性能指标去验收它("帧时间有没有降"),然后在指标没降时得出错误结论("subpass 没用")。而正确的验收指标是带宽与能耗("写回像素数降了多少"、"热稳态帧率提了多少"),这些指标下降而帧时间不变,才是设计意图被正确实现的标志。

从更长的视角看,移动端渲染架构正在收敛到一组与桌面端不同的优化目标函数:不是峰值帧时间的最小化,而是热约束下帧率的稳定化;不是带宽的绝对最小化,而是带宽、调度重叠与依赖链长度之间的三维平衡。subpass 只是这个目标函数里的一个变量,而理解这个目标函数本身,比掌握任何一个具体扩展要有价值得多。

9.3 发布前检查清单

如果你要在自己的项目里落地这套思路,下面这些是按优先级排序的检查项:

  1. 测出 DDD(单帧 DRAM 读写量),并区分其中有多少来自 pass 边界写回。这是所有后续判断的基础,也是第二节式子的可测版本。
  2. 对每个 tile-based render pass 算 tile 工作集,加上 20% 余量断言(规则一)。
  3. 开启 merge feedback 断言 + 交叉验证写回像素计数(规则二)。
  4. 把 transient attachment 的 storeOp 声明补齐——这是低风险高收益的一步(规则五)。
  5. 在 Mali-G710 之前的设备上重新评估延迟光照的算法选择(规则四)。
  6. 区分吞吐场景与延迟场景的 pass 粒度(规则三)。
  7. 检查是否有代码分支在依赖 tile 驻留的正确性(规则七)。
  8. 在 Snapdragon Profiler 里确认 Concurrent Binning 未被阻止(规则八)。
  9. 把验收移到热稳态(规则九)。
  10. 建立呈现时刻测量,把 jitter 作为一等指标(规则十)。

如果时间只够做一件事,我会选第 1 项:先测量,再优化。本文所有关于"subpass 可能更慢"的论断,都建立在"你知道自己要优化什么"这个前提上;而现实中大量 subpass 优化的失败案例,源于在没有测量的情况下把一个带宽假设当成了性能假设。


参考文献

  1. Arm, Tile-Based Rendering — Mali GPUs Documentation. https://support.arm.com/documentation/102662/0100/Tile-based-GPUs
  2. Peter Harris, Vulkan subpasses: the good, the bad, and the ugly, Arm Community Blog, 2025-11-06. https://developer.arm.com/community/arm-community-blogs/b/mobile-graphics-and-gaming-blog/posts/vulkan-subpasses-the-good-the-bad-and-the-ugly
  3. Khronos, VK_EXT_subpass_merge_feedback — Vulkan Feature Descriptions. https://docs.vulkan.org/features/latest/features/proposals/VK_EXT_subpass_merge_feedback.html
  4. Khronos, VK_KHR_dynamic_rendering_local_read — Vulkan Feature Descriptions. https://docs.vulkan.org/features/latest/features/proposals/VK_KHR_dynamic_rendering_local_read.html
  5. Khronos, Vulkan Render Pass — Vulkan Specification Chapter. https://docs.vulkan.org/spec/latest/chapters/renderpass.html
  6. Lionel Duc, VK_EXT_present_timing: the Journey to State-of-the-Art Frame Pacing in Vulkan, Khronos Blog, 2025-12-04. https://www.khronos.org/blog/vk-ext-present-timing-the-journey-to-state-of-the-art-frame-pacing-in-vulkan
  7. Qualcomm, Snapdragon Adreno GPU Overview — Game Developer Guide 80-78185-2 Rev AN, 2026-09-22. https://docs.qualcomm.com/bundle/publicresource/topics/80-78185-2/overview.html
  8. Qualcomm, Adreno GPU on Mobile: Best Practices — Game Developer Guide 80-78185-2. https://docs.qualcomm.com/bundle/publicresource/topics/80-78185-2/mobile_best_practices.html
  9. Qualcomm, VK_QCOM_tile_memory_heap specification. https://registry.khronos.org/vulkan/specs/latest/man/html/VkPhysicalDeviceTileMemoryHeapPropertiesQCOM.html
  10. Qualcomm, vkCmdBeginPerTileExecutionQCOM / vkCmdEndPerTileExecutionQCOM specification. https://registry.khronos.org/vulkan/specs/latest/man/html/vkCmdBeginPerTileExecutionQCOM.html
  11. Khronos, Vulkan Specification 1.4 — Core Revisions. https://vulkan.lunarg.com/doc/view/1.4.304.1/mac/antora/spec/latest/appendices/versions.html
  12. Samsung GameDev, Introduction to Vulkan Render Passes. https://developer.samsung.com/galaxy-gamedev/resources/articles/renderpasses.html
  13. Sascha Willems, Vulkan input attachments and sub passes. https://www.saschawillems.de/blog/2018/07/19/vulkan-input-attachments-and-sub-passes/
  14. Imagination Technologies, Does Tile-Based Deferred Rendering have a place in Desktop. https://blog.imaginationtech.com/does-tile-based-deferred-rendering-have-a-place-in-desktop
←返回文章列表

Related

可能也会喜欢

  • 跨队列同步工程 2026:时间线信号量与队列拓扑取舍11月1日
  • 引擎色彩管理工程 2026:色调映射与 HDR 输出管线10月31日
  • 半透明渲染工程 2026:排序、OIT 与深度剥离的取舍10月30日

Conversation

0 条

留下你的想法

加载评论中…

New comment