IMR、TBR、TBDR这三个缩写,第一次看到的时候,我还以为是什么新兴显卡型号的命名规则。后来做图形性能优化久了才意识到,这三个词背后其实是三种完全不同的“填像素”策略,而GPU跑得快不快、功耗压不压得住、甚至你的Shader应该怎么组织,都和它们直接相关。
很多开发者会跳过这个基础,直接去背“移动端要少用半透明”“桌面端无所谓”之类的优化口诀。但口诀背多了你会发现,换一个平台、换一类硬件,经验经常失效。说到底,还是得回到渲染架构本身:GPU到底是用什么方式,把几百万个像素画出来的。这篇文章就把IMR、TBR、TBDR掰开讲清楚,包括它们的原理、优缺点、常见误区,以及我在实际项目中总结出来的一些排错和优化思路。不论你是刚入门的图形学萌新,还是被移动端性能问题折磨过几次的开发者,都应该能从中找到些有用的东西。
1. 三剑客分别是什么:先从一条渲染流水线说起
1.1 所有GPU都共享的那条流水线
不管桌面还是移动,现代GPU的渲染流程大方向一致:应用程序提交三角形(或者说primitive,图元),GPU先做顶点处理,把模型坐标变换到屏幕坐标;然后进入光栅化,确定三角形覆盖了哪些像素;接着对所有被覆盖的像素执行片元着色,算出颜色、深度等信息;最后做深度测试、混合等后续操作,写入帧缓冲。整个流程可以简化成“几何处理 → 光栅化 → 片元处理 → 帧缓冲写入”。
IMR、TBR、TBDR这三个词,本质上是围绕这条流水线,在“何时处理”、“按什么单位处理”、“像素数据临时放哪”这三个维度上做出的不同选择。
打个不太严谨但好记的比方。把GPU想象成一家后厨。IMR是“现点现做”:顾客下单,厨师马上开火,做完一道端一道。TBR是“按桌分区备餐”:提前把餐厅划成几个区域,原料按区域分好,再集中烹饪。TBDR则是“按桌分区备餐,并且只做最终上桌的那份”——那些被遮挡、永远不会被看到的菜,直接不做了。
这个比方虽然糙,但方向是对的。理解了三者在同一流水线上采取的不同策略,后面所有优化建议就都有了解释。
1.2 为什么同一个“渲染管线”,能有三种架构
要回答这个问题,得看GPU所处的环境以及它们最在意什么。
传统桌面GPU有独立显存,内存带宽比移动端高一个数量级,散热和功耗预算也宽松得多。所以它更适合IMR:直接、灵活,把控制权交给开发者,渲染逻辑怎么折腾都行。
移动GPU则完全不同。没有独立显存,需要和CPU共享内存,内存走的是LPDDR这类低功耗总线,带宽有限,而且功耗直接决定手机烫不烫、续航长不长。在这种约束下,IMR那种“每个像素都频繁进出主存”的做法是灾难级的。TBR和TBDR就是为“省带宽”而生的:把整块屏幕切成许多小瓦片(tile),让颜色、深度、模板等数据尽可能留在GPU片上的高速缓存里,能少写一次主存就少写一次。
我在前几年帮某个团队调一款跨平台交互Demo时,遇到过一种很典型的情况:同一个模糊后处理,桌面独显上跑得毫无压力,一到移动端帧率直接砍半。刚开始以为是Shader复杂度,后来看性能计数器才发现,瓶颈根本不在float计算,而在带宽。从那以后,我判断移动端性能问题的第一反应就是先问:目标设备的GPU大致属于哪种渲染架构。
2. 立即模式渲染:把控制权完全交给开发者
2.1 立即模式渲染的核心流程
IMR,全称是Immediate Mode Rendering,立即模式渲染,是历史最悠久、桌面端最常见的架构。
它的工作方式是:每个绘制命令提交后,GPU立即按照“顶点处理 → 光栅化 → 片元着色 → 深度/混合 → 写回帧缓冲”的顺序,把当前这个三角形处理完。注意,这里的“立即”不是说一个三角形从头到尾独占所有硬件单元,而是说从架构语义上看,绘制命令是一条流水线流式执行下去的,先提交的图元先完成,不存在“攒一批再处理”的阶段。
帧缓冲位于显存中,颜色、深度、模板这些数据都需要从显存读取和写入。GPU内部当然有一层层缓存,但缓存的容量和覆盖范围有限,最终还是要频繁访问片外存储。
IMR的核心价值在于灵活。因为它按提交顺序逐图元处理,开发者可以自由切换渲染状态:今天开混合,明天关混合;先画不透明,再画半透明;同一个RenderTarget想反复读写多少次都行。对游戏引擎来说,这种自由度非常宝贵,复杂场景里各种渲染顺序要求都能满足。
2.2 立即模式渲染的优势与代价
优势总结起来有三点。
第一,驱动和硬件实现相对简单。不需要对三角形做分类、排序、跨瓦片调度,管线各阶段天然衔接。
第二,渲染顺序确定性高。提交什么顺序,基本就按什么顺序执行,混合结果可控,调试起来直观。这也是桌面GPU发展多年,底层API升级后依然坚持语义兼容的原因之一。
第三,状态切换的成本低。IMR的帧缓冲一直“在外面”,换一个渲染目标、换一套混合参数,只是改一下内存地址和管线状态,开销相对可控。
代价也很明显:带宽大户。
这里算一笔账。1080p分辨率下,一块RGBA8的渲染目标,一帧数据大约8.3MB;如果分辨率到4K,就是33.2MB。这还没算深度缓冲和模板缓冲。一次普通不透明绘制,至少要写一次颜色、读一次深度、写一次深度,也就是说一个像素就被访问了好几次。如果场景里有几个RenderTarget,又来几个全屏后处理pass,再加上overdraw(像素被重复着色)到2到3倍,一帧的内存访问量就奔着几百MB甚至上GB去了。
把这帧数据乘以60,再除以一秒,你会发现每秒需要几十GB的有效带宽。桌面独立显卡走GDDR这类高带宽显存,勉强扛得住;移动设备共享内存带宽只有几十GB每秒,还要和CPU抢,如果照搬IMR,那手机早就烫到没法握了。
2.3 桌面端为什么能一直用IMR
有人可能会问:既然IMR带宽消耗这么大,桌面GPU为什么不全部改成TBR?
因为桌面端的优势赛道不一样。独立显卡的显存带宽极高,功耗预算可以到几十瓦甚至几百瓦,IMR的带宽代价虽然存在,但被硬件能力抵消了一大部分。更重要的是,PC和主机上跑的图形应用千奇百怪,对渲染顺序的确定性、混合行为、计算与图形穿插执行都有很严格要求。TBR那套“先归类、后执行”的做法虽然省带宽,却会在某些场景下改变顺序语义,驱动要非常小心才能兼容。
当然,桌面GPU也没有完全躺平。近几代独立显卡在光栅化阶段也加入了不少基于小块(不是全屏瓦片)的缓存与调度优化,试图在保留IMR灵活性的同时,减少对显存带宽的依赖。但从架构分类上讲,它们仍然属于IMR阵营。这个细节后面对比时会再提。
3. 基于瓦片渲染:移动端省带宽的杀手锏
3.1 瓦片化的核心思路
TBR的全称是Tile-Based Rendering,基于瓦片的渲染。它的核心思路,是把屏幕划分成若干互不重叠的小方块,通常是16×16或32×32像素大小,然后逐瓦片完成渲染。
大致的流程是这样:顶点处理和几何阶段仍然要先跑一遍,把所有三角形的屏幕坐标算出来。但这之后,并不是立刻进入光栅化,而是加了一个“分块归类”阶段:根据三角形的覆盖范围,把每个三角形写入它可能影响到的所有瓦片的列表里。这个过程也叫binning。
每帧的前期阶段结束后,GPU有了每个瓦片的三角形列表。然后轮到真正的光栅化和片元着色,但这次是按瓦片逐个处理的。处理某个瓦片时,GPU会把该瓦片需要的颜色、深度、模板数据保留在片上一块高速内存里,片元着色、深度测试、混合都在这个片上缓冲区完成。等这块瓦片全部处理完,才把最终的颜色写回主存,再处理下一个瓦片。
这个设计最直接的好处就是带宽急剧下降。深度测试和颜色混合不再频繁访问片外内存,而是在片上解决,帧缓冲的写回变成“每个瓦片最终只写一次”。在IMR架构下,一个像素可能因为overdraw被读写好几次颜色和深度;在TBR架构下,这些中间过程都发生在片上,主存只看到最后的结果。
3.2 从“餐厅分桌”看TBR的隐藏代价
TBR不是没有代价,最典型的是几何工作的重复。
一个三角形在binning阶段,要先被处理一遍,把它的覆盖范围分到各个瓦片。到了每个瓦片的光栅化阶段,又需要它的顶点属性来做插值,因此顶点着色器往往要再执行一次,或者硬件需要缓存大量的顶点输出数据。顶点数不多的时候无所谓,但遇到高模、大量实例化绘制,这个“两次顶点处理”的开销就会被放大。
另一个问题是大三角形。如果一个三角形覆盖了200个瓦片,binning阶段就要把它写入200个瓦片列表里,列表索引本身要占带宽和内存。如果场景里有太多这种横跨屏幕的大三角形,TBR的优势会被抵消,甚至出现性能倒挂。这也是为什么移动端资料里经常强调“避免超大三角形”。
为了理解这个过程,我举个实际观察到的现象:全屏四边形如果作为后处理或粒子系统的载体,在TBR设备上可能表现很差,因为几乎所有瓦片列表里都有它。虽然每个瓦片最终都要画这个四边形,但“记录”这一步本身增加了额外负担。
3.3 开发者必须知道的TBR渲染目标语义
TBR还有一个只有优化时才会注意到的特性:它和现代图形API里的RenderPass概念是高度绑定的。
在移动GPU上,一个RenderPass的开始意味着让各个瓦片准备好片上缓冲区;一个RenderPass的结束,意味着把所有瓦片数据flush(冲刷)到主存。如果在RenderPass中间反复修改渲染目标,或者频繁把渲染结果Load/Store,就会产生大量不必要的瓦片数据读写。
我见过不少移动端性能问题,根源其实是“一段逻辑上连续的绘制被拆成了很多个RenderPass”。比如美术希望把场景颜色、深度、UI分开渲染,代码上没有合并,GPU就得反复执行瓦片缓冲的准备和冲刷,性能自然上不去。后来把能合并的绘制塞进同一个RenderPass,必要时用Subpass表达依赖,帧率明显改善。
4. 基于瓦片延迟渲染:把隐藏面消除变成“免费午餐”
4.1 TBDR比TBR多做了什么
TBDR全称Tile-Based Deferred Rendering,基于瓦片的延迟渲染。名字里的“Deferred”容易让人联想到延迟着色(Deferred Shading),但要提醒一句:它不是一回事。应用层的延迟着色是先把几何信息写进G-Buffer,再逐像素做光照的算法;而TBDR里的“延迟”,是硬件层面的一个调度策略。
TBDR继承了TBR的分块方法,同样把屏幕切成瓦片、同样先分块归类、同样在片上做混合和深度处理。但它多做了一件重要的事:在真正执行片元着色之前,先对所有已提交的片元做一轮可见性判断,只保留最终可见的片元,再进行着色。
这意味着什么?在普通TBR中,一个像素即使被后来的三角形遮挡,它的片元着色器可能已经跑过了,白白消耗了性能。而在TBDR中,这个被遮挡的片元在着色前就被剔除掉了,不执行Shader、不采样纹理、不产生任何片上写入。
这相当于硬件层面自动帮你做了“减少overdraw”这件事。对于大量三角形互相遮挡的场景,比如植被覆盖的地形、密集的建筑物,TBDR的收益非常可观。外界常说的“移动端隐藏面消除(HSR)”技术,本质就是TBDR架构的一部分。
4.2 延迟渲染电子顺带解决了深度带宽问题
TBDR的另一个特点是深度缓冲行为的高度优化。因为片元在进入着色器前就被比较过深度,最终写入深度的往往就是那个真正可见的片元。再加上瓦片内的深度比较在片上完成,深度数据根本不需要频繁读写主存。很多TBDR设备可以做到“深度缓冲永远在片上”,从而让深度相关的带宽开销趋近于零。
我调过一个带有大量阴影的移动场景,投影采用的是常规深度Shader技术。最初担心性能不足,毕竟阴影需要额外的深度渲染和采样。但实测发现,纯深度Pass在TBR/TBDR设备上的开销比预想低得多,因为深度Write这一过程基本不占用片外带宽,硬件在瓦片内就消化掉了。
当然,TBDR也不是万能的。半透明物体就是一个例外。硬件隐藏面消除处理不了透明度混合,因为半透明的正确绘制依赖严格的从远到近顺序,硬件不可能替应用决定“哪个半透明像素该覆盖哪个”。所以遇到半透明粒子、玻璃、烟尘这类特效,TBDR的优化效果会大打折扣,开发者仍然需要谨慎控制overdraw。
4.3 TBDR与“延迟着色”的词义岔路
这里值得再展开强调一次,因为讨论时经常有人被绕晕。
TBDR是GPU架构的类别,属于硬件层面的渲染组织方式。而延迟着色/延迟光照是应用层渲染算法,属于软件层面的实现策略。一个TBDR的移动GPU,跑Forward Rendering(前向渲染)也完全没问题,照样能享受硬件HSR带来的省带宽收益。而一个开发者写的Deferred Shading,在IMR和TBR/TBDR上都能跑,只是两个“deferred”的含义完全不同。
有一个小技巧可以帮你在讨论中快速分辨:如果对方在说“提前剔除不可见片元”“瓦片缓冲”“隐藏面消除”,那大概率在聊TBDR架构;如果对方在说“G-Buffer”“Deferred Lighting”“MRT”,那大概率在聊应用层延迟着色。
5. 参数对比与选型判断:IMR/TBR/TBDR到底差在哪
5.1 三种架构横向对比表
这一节把三种架构的关键差异放在一张表里,方便之后查。
| 对比维度 | IMR | TBR | TBDR |
|---|---|---|---|
| 片元带宽消耗 | 高,频繁访问显存 | 中,片上处理大部分颜色/深度 | 最低,额外剔除被遮挡片元 |
| 深度测试时机 | 通常有Early-Z,但受绘制顺序影响 | 片元着色前,瓦片内完成 | 片元着色前,硬件做全场景可见性判断 |
| 片元着色浪费 | 高,overdraw直接转化为Shader开销 | 较高,但带宽压力有限 | 很低,被遮挡片元通常在着色前剔除 |
| 顶点处理次数 | 一次为主 | 常见两次(分类+光栅化) | 常见两次(分类+光栅化) |
| 渲染顺序确定性 | 高,提交顺序基本等于执行顺序 | 中,驱动可做一定重排 | 中低,硬件可能基于可见性调整执行 |
| 状态切换灵活性 | 高 | 中,RenderPass切换代价大 | 中,且半透明等场景有额外限制 |
| 硬件复杂度 | 较低 | 中 | 高,需要额外调度缓冲 |
| 典型环境 | 传统桌独立显卡、高性能主机 | 早期移动GPU,部分嵌入式平台 | 现代移动GPU和部分低功耗平台 |
| 功耗控制 | 较差 | 较好 | 最好 |
这个表只是反映了主流设计上的差异,具体到某个GPU实现,细节可能会有调整,比如某些IMR设备也加入片内缓存来缓解带宽问题,某些TBR设备也会做局部隐藏面消除。但方向性的结论是稳定的:IMR重灵活、高带宽;TBR重分块、省带宽;TBDR在TBR基础上再砍掉overdraw。
5.2 为什么桌面GPU没有全面转向TBR/TBDR
看过对比表之后,桌面端为什么没有全面转向TBR/TBDR,答案就清晰了。
桌面的核心场景是高性能游戏和通用图形计算,开发者期望尽可能多地控制硬件行为。TBR把屏幕做了二分,意味着渲染并不是按照提交顺序在整个屏幕上推进,而是在每个瓦片内重新组织执行顺序。这种重排在大部分情况下没问题,但遇到复杂的混合、多重采样、计算与光栅化交替执行时,可能会引入兼容性和性能不确定因素。IMR的“直接”反而成了一种优势:所见即所得,调优路径清晰。
历史上也不是没有桌面端尝试过变体方案,比如把屏幕划分成小块做光栅化,或者利用更大的片上缓存分担显存压力。但它们中大多数在保留IMR语义的前提下进行局部优化,而不是彻底转向全屏幕瓦片化。这说明架构取舍不是“哪个省带宽就一定更好”,而是要在多种诉求之间找平衡。
5.3 移动端为什么几乎一边倒
移动端的答案也很明确:功耗和内存带宽是硬约束。
手机没有主动散热,电池容量有限,内存总线宽度受物理尺寸限制。TBR/TBDR能显著减少高耗电的片外内存访问,并且把深度、混合等高频操作放到片上,这对续航和发热控制都是决定性的。现代的移动GPU几乎都是TBR或TBDR路线,只是营销文档里不一定直接写明,有的厂家叫“瓦片渲染”,有的叫“隐藏面消除”,骨子里都是同一个思路。
正因为这样,移动端性能优化的很多经验都建立在“带宽贵、瓦片小、缓存有限”的假设上。如果你一味按照桌面IMR的思维去写移动端Shader,大概率会在overdraw和RenderPass管理上翻车。
6. 开发者实操:写出能适配三种架构的渲染代码
6.1 优化前的第一步:判断目标GPU属于哪一类
不推荐一上来就背各种优化口诀,先把目标设备的GPU架构搞明白再动手。
怎么判断?通常有三个途径。
第一,查硬件文档。设备或板卡的技术规格里,一般会标明“Tile-Based”还是“Immediate Mode”。如果文档说支持“Tile-Based Deferred Rendering”或者“Hidden Surface Removal”,那就是TBDR阵营;如果只强调并行渲染和光栅化能力,大概率IMR或TBR。
第二,通过Profiler观察带宽和Shader负载的比值。同一个场景,在IMR设备上,如果降低分辨率,帧率提升曲线会比较平缓,因为除了填充率下降,带宽压力也变了;在TBR/TBDR设备上,降低分辨率往往能大幅提升帧率,因为瓦片数量减少,binning开销和主存写回都跟着少了。
第三,用一些行为特征做“体检”。比如在全屏半透明粒子叠加场景里,如果性能下降得特别夸张,那多半是TBR/TBDR,因为半透明会绕开HSR优化,让所有片元都进入着色阶段;如果同样的窗口模式下性能表现很平稳,IMR的可能性更高。
6.2 一版通用的优化执行清单
不管目标设备是什么架构,下面这些步骤基本不会踩雷,但优先级我会做区分。
- 优先控制overdraw。这个在TBR/TBDR上尤其关键,在IMR上也能省Shader和带宽。怎么控?少用大范围半透明粒子、多用遮挡剔除、谨慎设计全屏特效、必要时把多个特效合成预烘焙成序列帧。
- 减少渲染目标切换。在移动设备上,频繁切换RenderTarget意味着反复冲刷瓦片缓冲;在桌面上,也会破坏缓存局部性。尽量把互相依赖的渲染操作合并到同一个RenderPass里,用Subpass表达数据的传递。
- 降低后处理分辨率。很多全屏效果并不需要全分辨率,半分辨率甚至更低就能满足观感。这一招对三种架构都有效,但对带宽敏感的TBR/TBDR效果最明显。
- 谨慎使用多重采样。MSAA在TBR/TBDR上因为有片上解析,代价相对可接受,但过高的采样数依然可能让瓦片缓冲压力剧增。如果只需要抗锯齿边缘,可以考虑MSAA 4x搭配硬件解析。
- 控制超大三角形。TBR/TBDR的binning阶段会按“三角形覆盖的瓦片列表”记录,超大三角形会让列表膨胀。真正需要全屏处理的后处理,尽量做成低分辨率或分块执行的版本。
- 保持绘制状态分组。把使用相同材质和混合模式的物体批量提交,减少状态切换。这在IMR上省的是状态切换开销,在TBR/TBDR上还能帮助驱动优化binning和排序。
6.3 一个真实的调优案例:后处理叠加半透明粒子
有次给某跨平台系统做效果Demo,场景本身不复杂,美术在界面上叠加了一层粒子特效,又套了一个模糊后处理。在桌面上跑得好好的,放到移动端测试机马上掉到30帧以下,而且机器发烫明显。
第一轮排查,先确认不是Shader浮点运算量过大。把模糊后处理的降采样从全分辨率改成半分辨率,帧率只回升了10%左右,说明瓶颈不在ALU。第二轮改用Profiler看带宽,发现RenderTarget读写和片元输出量异常高。这时候思路就比较明确了:模糊后处理本身要对整块RT反复采样,半透明粒子又叠了很多层,两者叠加导致overdraw非常严重。
优化方案分成三步。
第一步,把模糊后处理彻底低分辨率化,用四分之一分辨率跑,再上采样合并。全屏采样次数一下子降到了原来的四分之一左右。
第二步,把半透明粒子拆开处理。肉眼影响小的背景粒子,直接改成不透明绘制或预烘焙成一层静态纹理;真正需要半透明的部分,严格控制粒子层数,避免两三层全屏粒子叠在一起。
第三步,把模糊和最终UI合成放到同一个RenderPass里,通过Subpass完成,减少一次中间RT的读写和瓦片冲刷。
改完以后,移动端帧率从30帧上升到接近60帧,体感发热明显下降。这个案例也印证了我在优化中反复踩过的坑:如果代码层面没有意识到渲染架构的存在,很多“看起来昂贵的操作”在桌面端根本不痛不痒,一到移动TBR/TBDR上就变成性能黑洞。
7. 常见问题排查与避坑实录
7.1 六个典型症状与排查方向
下面这张表整理了我多次在GPU性能排查中遇到的典型问题,以及对应的排查思路。
| 症状 | 可能原因 | 排查方向 |
|---|---|---|
| 同场景桌面正常、移动吃力 | 带宽瓶颈在移动端被放大 | 看Profiler里的带宽和瓦片冲刷次数 |
| overdraw很高但帧率没崩 | 可能是TBDR做了隐藏面消除 | 检查HSR是否生效,重点关注半透明物体 |
| 半透明粒子一多就卡 | 半透明无法走隐藏面消除 | 减少粒子层数、预烘焙、限制全屏半透明范围 |
| 大三角形或全屏四边形很多 | binning列表膨胀 | 拆分大三角形,降低后处理分辨率 |
| 频繁切换RenderTarget后卡顿 | 瓦片缓冲反复Load/Store | 合并RenderPass,用Subpass替代中间RT |
| 顶点量很少但GPU占用仍高 | 片元着色或带宽吃紧 | 降低RT分辨率、减Shader复杂度和纹理采样数 |
这个表只是给一个初始判断路径。实际项目里往往多个问题叠加,需要逐个排除。排的时候我养成了一个习惯:每改一步,只看一种变量。比如先固定分辨率,再动Shader;或者先固定Shader,再动RenderPass结构。这样做才能得出可靠的结论。
7.2 踩过的一些坑和心得
第一个坑:以为TBR/TBDR对半透明“自动优化”,结果特效叠到飞起。早期我在某个项目里给角色加了一堆粒子拖尾,想着移动端TBDR不是号称能消除overdraw嘛,半透明总该没事。实际一测,卡得像幻灯片。因为半透明物体只要开启了混合,就无法被硬件提前剔除;每一层粒子都要真正着色并混合,overdraw分毫不少。从那以后,我遇到移动端半透明特效,第一件事就是数“全屏叠加层数”,超过两层就开始考虑预烘焙或者改材质。
第二个坑:把RenderPass当成无所谓的东西。图形API里RenderPass这个概念,在桌面IMR上其实更像一个规范约束,性能差别没那么明显;但在移动TBR/TBDR上,它就是瓦片缓冲的生命周期管理器。有人图省事,每帧把模型按材质分开,一个材质一个RenderPass,结果性能惨不忍睹。把相关内容合并进一个RenderPass后,帧耗时降了差不多三分之一。要记住,Load操作意味着把主存里的历史数据搬进瓦片缓冲,Store操作意味着把瓦片结果写回主存,这两种操作都要钱。
第三个坑:忘记检查深度缓冲的行为。有些场景开了额外RT,又设置了很多次深度读写,导致带宽被悄悄吃满。因为在TBR/TBDR上,深度信息通常能留在片上,但如果代码里存在“读回深度做软粒子”“深度纹理采样”之类的操作,就会强制硬件把深度缓冲导出到主存。这类操作能避免就避免,非做不可时,尽量降低采样深度纹理的分辨率。
7.3 快速判断设备架构的三条经验
项目环境里经常没法立刻拿到详细文档,这时可以靠几条经验快速判断。
经验一:看设备功耗曲线。一个渲染场景,如果设置界面里切换动态模糊或全屏后处理时,帧率变化剧烈且温度随之飙升,很大概率是TBR/TBDR,因为后处理直接拉高了带宽。
经验二:观察Shadow Pass的表现。如果场景阴影质量从低到高,帧率下降成倍增长,但在某些设备上却很平缓,说明那些平缓的设备对深度Pass有特殊优化,也更可能是TBR/TBDR。
经验三:用MSAA做试探。同一款游戏,在设备A上开启4x MSAA只掉10%帧率,在设备B上直接掉30%,那么A多半是瓦片化架构,在片上完成了多数解析工作;B更接近IMR或者瓦片缓冲带宽受限。
这些经验不是百分百严谨,但用来做前期判断和性能预估,已经够用了。
8. 一点个人体会
做了这么多年图形和渲染相关的工作,我最深的一个感受是:与其背一堆“移动端优化十条”,不如先把目标平台的GPU架构模型在脑子里搭出来。
知道它是IMR,你自然会理解状态切换为什么重要、显存带宽为什么是瓶颈;知道它是TBDR,你自然会重视RenderPass合并和半透明层数,因为你清楚瓦片缓冲怎么运作、隐藏面消除对谁生效。架构知识让优化动作有了依据,而不是靠玄学试错。
最后再分享一个小技巧。如果你需要快速评估一个场景在不同架构设备上的带宽压力,可以在Profiler里同时关注两个指标:片元着色器输出量(Fragment Output)和主存读写带宽(Memory Bandwidth)。如果两者都很高,说明overdraw和RT切换在大量消耗资源;如果输出量不高但带宽很高,问题大概率出在纹理采样或者渲染目标读写上;如果输出量高但带宽很低,恭喜,你多半跑在一台TBDR设备上,剩余的主要矛盾变成了Shader运算量。把这套思路用熟,很多性能问题其实一眼就能看到方向。