Tile-based Rendering Architectur
在松弛中打开自己,内心阔朗透气,对外界保持开放度,客气不争,但静守原则,慢慢让自己堆积成形,自性光明 —— 黎戈《心的事情》
0. GPU的Rendering Architecture
0.1 SOC硬件架构
在介绍GPU的渲染架构之前,先让我们来看一下现在计算机的SOC(System On Chip)硬件架构。如下图所示:
现在SOC一般都会集成一块CPU与一块GPU。CPU主要是为一些串行的、flow-control运行逻辑的、内存访问低延迟的场景所设计,其性能改进方向针对的主要是分支控制以及内存cache等模块;而GPU则是为一些并行处理的,无分支运行逻辑的场景所设计的,其性能改进方向主要针对的是寄存器以及算术处理单元(ALU)模块。
0.2 Single Instruction Multiple Data
在逻辑执行实现上,CPU主要面向的是具有频繁的分支跳转结构与循环控制结构的应用场景,一般是在单个线程中处理一堆数据,而GPU面向的则是在不同的数据上执行相同代码逻辑的应用场景,一般是对需要处理的数据分割成不同的数据单元,之后分拆到多线程中,进行并行执行:比如对于图形学中的Vertex Shader以及Pixel Shader等,就需要对不同的Vertex/Pixel执行完全相同的代码逻辑。为了提升其执行效率,就需要在多个不同的线程上同时执行相同的计算机指令,这就是所谓的Single Instruction Multiple Data(SIMD)。
相对于传统CPU的串行执行结构来说,SIMD的优势在于,可以使用单条数据的处理时间消耗,完成多条数据处理的效果,这就极大地提升了执行效率。通常来说,相对于CPU的消耗,GPU的执行效率通常会高出多个数量级(即至少成百上千倍的效率提升)。
SIMD的实现方式主要有两种:Vector-Based以及Scalar-Based。这二者的区别在于处理粒度上的不同:Vector-Based指的是将Vector作为最小处理单元,而Scalar-Based指的是将单一的数据元素作为最小的处理单元。
Vector-Based SIMD的优点在于执行非常高效。其主要用来处理那些需要一次性对多个元素(大多数是二、三、四个元素)进行相同指令操作的问题,对于GPU中大量的颜色以及顶点数据处理情景,这种模式就非常合适。其弊端在于,假如需要处理的数据个数不足以填满其基本处理单元一次性能够处理的元素个数,那么就会存在浪费:比如某个处理单元的处理元素个数为4,而传入的参数是Vector3,那么就会有25%的浪费,而如果传入的是Vector2,就会有50%的浪费!这里的浪费指的是电力以及性能的损耗,毕竟处理单元的部分结构处于空转状态。通常解决这种问题的方式为,对程序代码进行优化,尽量保证Vector-Based处理单元的满载状态,但这会带来程序实现上的负担,且对于一些新手或者部分无法满载的情况而言,这种方式也就失效了。
相对于Vector-Based处理方式来说,由于不需要考虑Vector的满载运行,Scalar-Base处理方式就要灵活很多。虽然在Vector类型的数据结构上,Scalar-Based处理方式的效率不如Vector-Based处理方式,但是由于硬件的处理能力实际上是一样的,所以,在非Vector类型的数据结构的处理上,Scalar-Based处理方式的浪费就要少很多,其输出的效率就要高出Vector-Based处理方式不少。
0.3 GPU架构概览
按照使用硬件来划分,GPU可以分成桌面GPU与移动GPU两种,前者主要应用于图形工作站以及个人PC等桌面设备,而后者则应用于手机,平板等可移动设备上:这类GPU芯片尺寸小,且需要照顾到移动设备电池续航能力的限制,所以通常需要牺牲部分带宽(Note:在移动平台上增加GPU计算能力相对增加带宽而言是比较容易的事情,增加多个GPU Core就能够做到,但是经常受限于移动平台有限的带宽,即使有了足够强大的计算能力,其表现也上不去)与性能。由于使用场景与特点的不同,这两者在使用方式如渲染架构上也有所区别。
渲染架构按照数据的硬件处理单元的不同,可以分成Unified以及Un-Unified两种。Unified Shading Structure指的是不论是Vertex Shader(VS)也好,还是Pixel/Fragment Shader(PS)也好,都是由相同的处理单元进行计算的。而Un-Unified Shading Structure则是对于Vertex-Shader与Pixel/Fragment Shader的数据计算则是各由不同的专门的处理单元负责完成的。
如上图所示,我们可以看到,从时间轴上看,Unified中,VS与PS是交替穿插执行的,谁有需求谁就上。而Dedicated中,则是各人耕各田,无田自休闲。显然,从执行效率的角度来说,Unified框架要高于Dedicated框架。另外,从VS/PS的复杂度来分析,也可以得知,Unified框架能更好的适应具有不同VS/PS负载比的情况,不论是VS Bound(瓶颈在VS的情况,称作VS Bound)的情况还是PS Bound的情况,Unified框架都能够很好的hold住。
此外,根据硬件渲染架构来划分,GPU又可以划分成三类:
-
Immediately Mode Rendering(IMR)
-
Tile-Based Rendering(TBR)
-
Tile-based Deferred Rendering(TBDR)
1. IMR的渲染流程及其在移动平台上的表现
桌面GPU基本上使用的是IMR,甚至部分移动GPU(如NVIDIA的GeForce ULP和Vivante的GC系列GPU)也是使用IMR。所谓的IMR,说的是GPU完成某个物体或者某个三角面片的渲染之后,就会将渲染结果写入FrameBuffer中,之后就开启下一个物件的渲染流程。其渲染流程可以用下述的伪代码所概括:
for draw in renderPass:
for primitive in draw:
for vertex in primitive:
execute_vertex_shader(vertex)
for fragment in primitive:
execute_fragment_shader(fragment)
IMR渲染架构框架如上图。
假设某个物体A完全被物体B所遮挡,但是A早于B渲染,那么在A渲染完成后,再进行B的渲染,就会出现B的渲染结果将A的渲染结果完全覆盖的情况,这种时候,GPU对于物体A的渲染的所有消耗都成为白费,这就是所谓的OverWrite。
开发人员常常通过使用Early-Z的方法来降低OverWrite。所谓的Early-Z,指的是在渲染之前对物体或者三角面片进行排序,使得在深度上更为靠近相机的物体或三角面片先渲染,从而将靠后的物体或者三角面片通过GPU自带的深度Test方式剔除掉来避免后续的计算消耗的一种方法。但是这种方法的效果表现通常都跟各个具体的场景有关:有的表现好,有的表现差。上图中的Early Visibility Test就是此处的Early-Z。
当然,前面所说的A被B完全遮挡住的情况是一种比较极端的假设,在实际情况中可能A并未被B完全遮挡,而且也可以通过软件对需要渲染的物体按照距离相机的深度进行排序来极力避免这种浪费的产生,但是这种做法一方面是会引入排序的消耗,另一方面也无法完全避免这种浪费。
此外,在一些常见的复杂渲染算法中,IMR往往需要处理大量的数据,而这些数据通常是无法完全塞入GPU的Cache的,因此经常会需要与系统内存进行交互读写(如深度buffer,color buffer,stencil等等),而这个过程是比较慢的,且对带宽有着较高的要求,同时还会产生较高的能耗。虽然通常在桌面应用中,都会对这些传输的数据进行压缩处理,但实际上压缩后的数据量依然不容小觑,因此不适合在移动设备这种电量有限且带宽较低的硬件上使用。
由于使用IMR架构对带宽与能耗有一个比较高的要求,而当前移动平台上由于电量的限制,导致GPU与CPU是共享一块内存的(所谓的Unified Memory Infrastructure),导致移动平台上的带宽也是严重受限的,所以目前大部分的的Mobile GPU(ARM公司的Mali GPU,高通的Adreno,Imagination的PowerVR等)都不能支撑IMR的消耗,必须另谋出路。
为了解决上述两个问题,Tile-based Rendering(TBR)的GPU框架应运而生,而当前的移动平台的渲染架构目前基本上都是采用 Tile-based Rendering(TBR)的渲染模式。那么什么是Tile-based Rendering,这种GPU架构怎么解决带宽与能耗的问题,Tile-based Rendering与Tile-based Deferred Rendering之间的Deferred是什么意思,这两者又有何区别呢?
2. TBR
在GPU中有一块超高速的On-Chip芯片,其作用类似于常说的Cache,此芯片的容量较小,最小可以去到16*16像素块大小。而所谓的Tile-Based Rendering,就是将以往IMR与系统内存进行频繁交互的过程迁移到这块高速On-Chip Buffer中完成,避开上面的那些阻碍性的慢速操作,从而实现渲染加速的一种Rendering架构。
由于On-Chip芯片容量较小,无法将整屏的FrameBuffer数据全部塞进去,所以为了充分利用On-Chip芯片的高速优势,就需要将IMR中的FrameBuffer分割成一个个的互不重叠的小方块Tile,每次只渲染一个Tile,这样在渲染每个Tile的时候,所有需要的数据如Color、Depth等都能够从On-Chip芯片中取得,从而避免与系统内存进行缓慢的交互处理。在Tile渲染完成之后,就会将其结果输出到FrameBuffer中对应的区域。当所有Tile都渲染完成的时候,FrameBuffer上的数据就是需要显示的整个画面的数据,如下图所示:
注意,实际上Tile的尺寸并不一定需要是方块,实际上,PowerVR SGX的Tile的长宽是不相同的。Tile-Based Rendering的渲染架构图如下所示。
参考上面的TBR的渲染流程图,可以文字简述如下:
-
CPU提交需要渲染的三角面片数据到GPU,GPU对每个三角面片调用Vertex Shader进行处理
-
经过VS变换后,会得到三角面片对应的投影空间中的位置,根据这个位置,进行Clip/Cull处理
-
Tiling处理
-
在TBR中,GPU需要将一帧中提交的与各个Tile相关联的所有三角面片搜集起来,之后做统一处理:这个过程在早期固定管线GPU上是通过软件完成的,现在许多可移动平台GPU已经设计了专门的硬件来处理这个操作(如PowerVR的tiler),具体来说:
-
对于每个可见的三角面片,根据gl_position的输出语义决定哪些tile可能会与这个三角面片有交集,从而将这个三角面片的索引保存到相关tile的Triangle List中
-
对于每个三角面片,将处理后的数据存储到系统内存中,这些数据包括:顶点位置,VS的输出,三角面片索引,fragment状态以及其他与Tile相关联的数据结构。
-
三角面片在后续渲染中可能需要用到的数据对于各个相关tile而言都是完全相同的,没有必要单独拷贝,只需要存储一份即可,使用的时候,Tile直接按照索引即可进行访问
-
此处这些数据结构以及各个Tile的Triangle List都被存储在一个称为frame data(在ARM文档中对应的是Polygons List,而Imagination Tech文档中则是对应Parameter Buffer)的临时结构中,以备后用
-
-
由于数据量较小,所以此处的写内存操作对带宽的依赖较小,不会成为瓶颈,而且越是复杂的场景中,其带宽节省的也就越多。当然,前提是提交的三角形数目维持在一个合适的范围内,如果数目过多,带宽需求必然增加,导致渲染速度大幅降低,严重时,甚至低于IMR,这一点需要注意。
-
此处的带宽消耗还可以通过压缩进一步缩减
-
-
-
在渲染的时候,针对每个Tile,将其对应的三角面片列表及其对应的数据放入GPU进行渲染
- Depth/Stencil Testing以及Alpha Test/Blend等高消耗操作都在此时完成,由于缓存的介入,这些高消耗操作的消耗得到了大大降低。
-
渲染完成后,将结果写入对应的FrameBuffer区域
-
这也是TBR中需要依赖带宽的地方,不过这时候的数据量也比较小,受带宽限制的可能性也比较小
- 此时写入的数据中不包含Depth/Stencil,也不包含MultiSample Buff数据,也没有OverWrite的数据
-
如果用伪代码来表示,TBR的渲染流程可以分成两个Pass,在第一个pass的时候,GPU将提交过来的所有DrawCall都收集起来,经过Vertex(Shader)+Tiling处理后存储内存中的Frame Data结构中;之后在第二个pass的时候,逐Tile逐Tile的将Frame Data数据从内存中读取出来,之后完成Rasterization + Fragment Shader相关的渲染操作:
# Pass one
for draw in renderPass:
for primitive in draw:
for vertex in primitive:
execute_vertex_shader(vertex)
append_tile_list(primitive)
# Pass two
for tile in renderPass:
for primitive in tile:
for fragment in primitive:
execute_fragment_shader(fragment)
按照这种描述,对于那些跨越多个Tile的三角面片来说,就需要被渲染多次,按理来说,应该比只渲染一次的IMR要更慢才对,到底是如何实现其渲染的加速的呢?实际上,其能做到加速主要是由于以下几个因素:
-
从上图的渲染流程中,我们可以看到,相对于IMR而言,其带宽需求最高的操作,以及与内存读写最频繁的操作都是在On-Chip Buffer上完成的,如此则避免了带宽过低而造成的时间延迟,同时也降低了电量的消耗。
-
此外,由于是按照tile来渲染的,每个tile中的像素数据的关联度远远高于整个屏幕空间中像素之间的关联度,导致在进行计算的时候,由于高关联度导致的cache miss也大大减少,从而提升了计算效率
-
在渲染的过程中,有些要素如UI等是固定不变的,在TBR的实现过程中,可以通过调用相关的函数判定将要绘制的Tile与上一帧是否相同来决定是否需要重新绘制(how?),减少了需要绘制的内容
TBR也有着自身的一些不足:
- 需要在Vertex Shader之后,将中间数据写入DDR(之所以要写入系统内存,是因为GPU缓存尺寸太小,无法装下所有的中间数据)
TBR跟IR的渲染逻辑流程如下图所示,其中上半部分是TBR,下半部分是IR:
3. TBDR
在TBR的渲染流程中,由于On-Chip 芯片的介入,使得对于各个面片的处理过程避免了与内存的频繁交互,使得写入FrameBuffer的数据都是最终会显示到屏幕上的数据,避免了IMR中的OverWrite,但是却没有避免OverProcess,即重叠的面片还是需要经历一道处理工序,虽然其结果并没有输出到FrameBuffer中。
PowerVR在TBR的基础上,增加了一种叫做Hidden Surface Removal(HSR,Mali上类似的技术名字叫Forward Pixel Killing)的硬件(如下图所示),可以对每个Tile中三角形列表进行检测,提前将被遮挡的三角形剔除出去,从而避免这些无效数据对pixel shader造成的消耗,进一步提升性能,这就是所谓的TBDR。
之所以PowerVR有勇气称他们的技术为Truly TBDR,是因为他们对Defer的解释是将所有像素相关的处理过程都Delay或者说Defer到其Visibility属性都已经完全清楚知道之后再进行,如此就能完全避免无谓的OverWrite了。而在IMR或者TBR中,对于被遮挡面片或者物体的处理,是通过软件算法在CPU中完成的,而PowerVR通过增加硬件的方式大大加速了这个过程,对OverWrite的侦测与剔除流程也更为完善:保证了只有那些在屏幕中可见的像素才会被传递到Fragment Shader阶段进行着色处理。
3.1 Tiler
我们之前说到,对于一帧中提交到GPU的所有三角面片的Tile处理,在PowerVR的结构中是通过Tiler硬件完成的。下面我们来看下Tiler的主要的工作流程。
在每一帧中,提交到GPU的所有面片都会按照用户定义好的逻辑如Vertex Shader进行变换处理,最终我们得到的实际上是屏幕空间中的数据。PowerVR中的Tile Accelerator就会根据此时的位置,判定当前的三角面片都落在了哪些Tile上面。并将这个三角面片索引添加到对应这些Tile的Triangle List中。而各个的Triangle List以及变换后的三角面片的数据都存储在一个临时存储结构中,这个结构在PowerVR中叫做Parameter Buffer,也就是我们刚才所说的Frame Data。
跟Frame Data一样,Parameter Buffer同样也是存储在系统内存中的,且包含了渲染一个Tile所需的全部数据。
3.2 HSR(Hidden Surface Removal)
在传统的IMR渲染框架中,Override带来的消耗主要有两方面:不可见像素的渲染成本以及将这些无效像素数据写入FrameBuffer的带宽消耗。如前所属,TBR通过On-Chip芯片解决了后者的消耗问题,但是前者无效像素的Shading消耗还依然存在,因此对三角面片进行排序,按照深度顺序进行渲染,借用early-Z的优势,依然能对帧率的提升产生作用。
PowerVR的HSR技术可以避免上述不可见像素的shading消耗,具体来说,在进行fragment shader之前,HSR就会对三角面片进行处理,确保只有会显示在最终屏幕中的像素才会参与到后续的计算当中。具体的执行过程类似于Z Test,如下图所示,对于不透明物件,只保留最近的像素,对于透明的物件而言,其对应的像素数据都会被保留。
HSR原理Mali家的Forward Pixel Killing(FPK)技术在实现上更加接近Depth Test的逻辑,如下图所示,对于每个处于Tile覆盖之下的像素(更准确来说是quad),都会将之传入FPK Logic单元,在这个过程中会与已经存入到FPK Buffer对应位置的最近depth数值进行比对,只有当不远于当前已存储的数据,才会进入后续的shading过程,并同时更新FPK Buffer中的Depth数据。
FPK关于FPK更进一步的细节请移步参考文献[10]:
FPK示例
- 如果Raster新产生的quad pass test,并且quad的4个pixel被fully covered,那就把与该quad具有相同位置的,更早的(意味着更远)那些thread全终止(它们可能还在FPK Buffer或已经在近Fragment Shader了)。
- 另外,当quad被两个较近triangle组合起来cover到时,较远的triangle对应该位置的quad也不需要做shading。因此为进一步优化,Mali保存了整个tile所有Quad最近一次的coverage,如果FPK新近的quad不是full covered,但与该quad最近的一次coverage相或后是full coverage,则类似1),要把更早的thread全终止,即发出kill信号。
-- ~ -- ~ -- ~ -- ~ -- ~ -- ~ -- ~ -- ~ -- ~ -- ~
作者:xiaocai
链接:https://www.zhihu.com/question/49141824/answer/136096531
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
经过HSR之后,需要shading的像素数目就大大减少了。因为HSR是将最终需要显示到屏幕中的像素进行输出,那么会影响到这一判断的一些操作如discard,sample masking,alpha testing,alpha-to-coverage以及alpha blending都会对结果产生影响,使得HSR的优化失效。所以为了最大化HSR的性能提升效果,就需要竭力避免上述操作,即使因此需要增加state change也是值得的。即使真的不能避免,最好也要将这些物件的渲染顺序进行限定,从前往后依次是:Opaque->Alpha Test-> Alpha Blend
在参考文献所举的例子中,对于一个画面平局OverWrite数为4.7的场景,经过PowerVR的HSR处理后,可以将OverWrite降低到1.2,看得出来,HSR的效果还是很强劲的。
对于那些并非PowerVR的硬件来说,也可以通过增加一个depth pass的方式来模拟HSR(实际上,HSR的实现流程也是相仿的,只是结果更为精确):
- 先开启Depth Writes,关闭Color Writes,并用一个及其简单的Fragment Shader,来实现Depth Buffer数据的获取
- 之后进行正常的Fragment Shader,只是这一次可以关闭Depth Writes
通过第一次pass拿到的Depth Buffer,可以有效避免第二次pass中无效像素在复杂Fragment Shader中的消耗。
这种技术在IMR中经常使用,同样,在TBR中也有很好的效果,只是在这两种模式中的表现有所不同:IMR中,Depth-Only Pass会需要写Depth Buffer,这就会有带宽消耗;而TBR中,Depth数据实际上是存在Frame Data中的,所以没有这部分消耗;不过因为需要走两次Pass,所以在TBR中,可能需要额外存储一份Frame Data。
4. TBR的渲染流程
从之前的TBR、TBDR的渲染架构图,我们可以看到实际上渲染的三个步骤:CPU,Vertex Shader,Fragment Shader三者其实是没有太大耦合的,出于性能考虑,可以考虑按照流水线的生产方式将之错开放置在三帧中完成,渲染流程如下图所示:
这张图中绘制的三种操作在每帧中的耗时看起来是相同的,实际上情况可能比这个复杂一点:三种操作号是各不相同,且具体耗时情况随着渲染内容的不同而有所不同。
这种流水线型的渲染结构会有以下一些问题:
-
用户交互可能会存在一定延迟:0帧点击某个按钮,可能要到2帧才会响应
-
数据同步操作可能会有所限制与延迟,比如:
-
需要在没有pixel pack buffer的场景中调用同步数据查询接口glReadPixels,此时会Stall CPU并等待结果返回,此时会有极大的延迟,如果想对此进行优化,可以考虑使用多个framebuffer在多帧进行轮换使用的方式来避免读取当前帧的数据结果,从而解决CPU Stall的问题;
-
调用Occlusion query这种异步数据查询接口也会有问题:此时需要等待比较长的一段时间才能返回有效数据。在传统IMR中的那种在固定帧数中就会有返回结果的情况,在TBR中就不存在了。
-
CPU需要对已经提交到GPU中进行渲染的数据进行更新,可能会需要等待。如动作数据可能每帧都需要更新,但是在GPU对数据进行更新的时候发现,这个数据仍然在被GPU使用中,那么就会存在等待与延迟。对于这种情况,一般的做法也都是复制出一份拷贝空间,对这些拷贝空间进行循环使用操作。但这种做法对于一些内存吃紧的设备来说,可能会比较奢侈。
-
-
有两个扩展接口需要加强关注:EGL_KHR_image_pixmap以及GLX_EXT_texture_from_pixmap。在使用这两种接口的时候,操作系统一般都不会去动其对应的资源占用的内存,而是不得不选择将一部分渲染结果flush到FrameBuffer中,或者将整个PipeLine挂起以等待上述两种操作完成。
5. TBR/TBDR使用建议
TBR与传统的IMR的实现方式的不同,决定了其使用方式也存在差异。因此在使用TBR的时候,有一些注意事项,总结归纳列举如下:
- 必须要保证每一帧被正确的终止,从而使得Frame Data被清空
与IMR渲染的中间结果是放置在FrameBuffer中不同,TBR处理的所有中间数据都是frame data,frame data的尺寸与提交到GPU的三角面片数成正相关,三角面片数越多,frame data的尺寸越大,在每帧正常终止的时候,会清空frame data,所以在使用TBR的时候,必须要保证每一帧都会被正确的终止,否则可能出现frame data持续增加而导致性能快速下降的表现。实际上,当三角面片书过多,就会导致驱动对frame data进行一次强制的flush刷新操作,将数据写入到内存中,等到进入fragment shader阶段的时候,再从内存中读取出来。这种情况下消耗的带宽至少会占用16倍的正常flush所用带宽大小。从这一点看来,三角面片的数目与渲染消耗的时间并不是一种线性关系
-
在TBR中可以无需避讳Clear的消耗,大胆使用
-
在IMR中,Clear会对FrameBuffer中的每个像素进行一次写操作,是一个非常高消耗的指令,开发者常常会根据实际情况避免使用这个指令(如假设某个FrameBuffer的数据已经不需要了,且后续对于这个FrameBuffer的写操作都是无条件的写入,那么此时就不再需要对此FrameBuffer进行Clear;又比如可以在相邻两帧中交替使用Depth Buffer的高低位数据空间,在使用高位空间时顺便写入0到低位空间,从而避免对DepthBuffer的Clear)
-
在TBR中,由于所有写入FrameBuffer的数据都来自于FrameData(在PowerVR中,这个数据较arguments buffer,在ARM上,这个数据较polygon lists),所以Clear FrameBuffer,对应的只需要清理掉当前的FrameData即可,这个操作非常的高效,甚至还因为抛弃了冗余的FrameData而提升渲染效率,所以在需要使用Clear的场景中,不要刻意避免(比如Scissor,Color Write Mask以及Clear部分数据之类的会阻止全盘Clear的操作应该尽量避免)Clear操作,反而应该大加提倡。(因为针对IMR而进行的减少Clear的优化操作,在TBR上甚至可能造成帧率下降为1/4的严重后果)
-
对于那些既需要在PC上使用IMR进行渲染,有需要在手机等移动平台上需要使用TBR进行渲染的场景来说,就不能直接使用glClear指令了
-
glClear指令是一个底层指令,无法领会上层的意思,所以不能甄别这个GPU是什么架构的,到底是否需要对FrameBuffer进行清理,调用这个指令会不管三七二十一,直接就对FrameBuffer进行清理操作。
-
可以考虑使用EXT_discard_framebuffer扩展,这个接口会告诉GPU,某些Buffer的数据我不用了,你想怎么处理就怎么处理:如对于PC等IMR平台,就会直接将这些buffer设置为discarded;而对于移动平台,则会释放掉Frame Data。
-
-
对于那些render-to-texture的操作,如environment map需要使用临时depth buffer,可以考虑在渲染完成之后,与framebuffer解绑之前,调用glDiscardFramebufferEXT接口,TBR收到这个消息后,就不会将深度数据写入到内存中。
-
当然,Clear之后FrameData就会被清空,意味着如果之前的工作没有被写入到FrameBuffer的话,就会全部丢失。所以通常都是在一帧的最开始调用这个接口,避免卷入上一帧的垃圾数据而影响性能
-
-
尽量避免保留上一帧的FrameBuffer数据。
在一些应用中,常常因为上一帧的数据内容可以重用,而选择保留上一帧的内容,但是这种操作可能会导致一些额外消耗:渲染开始的时候,Tile Buffer会需要从之前保留的FrameBuffer中读取颜色数据,就产生了带宽消耗以及时间消耗。当然,如果绘制保留内容的时间远远大于拷贝之前Buffer数据的时间,那么保留上一帧的数据内容还是很划算的。高通为了处理这种情况,特意增加了一个扩展接口,EXT_discard_frame_buffer,通过调用这个接口,可以指定哪些区域的数据需要更新,具体可以参考这篇文章。
- 尽量避免在渲染中期进行FrameBuffer数据读写。
比如进行一些会导致frame data被flush到FrameBuffer的操作如eglSwapBuffer, glFlush, glFinish, glReadPixels, glCopyTexImage, glBlitFrameBuffer, query occlusion in current frame, render-to-texture(有些驱动中,glBindFrameBuffer也可能会导致对FrameBuffer的Shading操作,因此每帧中最好只调用一次bind操作)等,总结起来,这些操作可以概括为
- 显式的写buffer操作
- 读取buffer操作
此外需要注意的是,即使这些操作中的部分操作看起来像是全程在GPU中完成,比如从pixel pack buffer中调用glReadPixel操作,也是会有很大损耗,为啥呢,因为每个draw-then-access操作,都会触发fragment shading,从而造成消耗。
-
Alpha Blending在TBR中的成本相对于IMR有明显下降
-
IMR中Alpha Blending由于需要进行read-modify-write操作,导致需要与系统内存进行交互操作,比较耗时;
-
在TBR中,这个过程则是在On-Chip芯片上完成,导致其成本无限降低,在部分具有针对性设计的硬件上,其消耗甚至可以降低到零。
-
上面说的消耗都是指渲染半透或者透明像素的直接消耗,对于由于这些像素引起的间接消耗如导致HSR的优化程度降低等却依然存在,因为对于不透明物体,其HSR流程会比较顺畅,而当遇到透明或者半透物体时,就需要将之前通过HSR深度检测的所有像素都flush到shader core进行计算,以保证后续透明像素的blend能取到正确结果。从这个角度看,尽可能的避免透明或者半透像素的物件对于提升性能表现依然是一个有效策略,如果实在无法避免,就将这些物件的渲染顺序放在不透明物件之后。(这个结论对Alpha-Test也依然有效)
-
-
MultiSampling的消耗对比与使用方式:
-
在IMR中,MultiSampling往往会导致写FrameBuffer的带宽按倍数上涨,如4X的MultiSampling,其带宽消耗也将按照4X计算
-
在TBR中,MultiSampling的过程是在On-Chip芯片上完成,因此,其带宽消耗就可以省略了,但是还是会有一些其他的消耗依然是不可忽略的,主要来说,有两方面
-
4X的MultiSampling,会导致Frame Data的数据量也变成4X,这在On-Chip芯片尺寸固定的前提下,就意味着Tile尺寸的缩小,而Tile尺寸缩小,就意味着每个FrameBuffer对应的Tile数目的增加,最终导致整体的渲染时间的增加。当然,将Tile尺寸缩小为原有Tile尺寸的1/2,其渲染时长并没有增加到原有渲染时长的两倍,尤其是对于一些受限于Fragment Shader消耗的场景来说,可能渲染时长都不会有太大变化。
-
MultiSampling会导致在物体轮廓处的像素的采样数目增加,对于一些横跨两种shader材质的像素来说,假设之前只用shading一次,那么此时可能就需要shading两次,如下图所示:红框包裹部分,在没有MultiSampling的时候不需要进行shading,在MultiSampling的时候就需要进行shading。在这种情况下,HSR能够剔除的像素数目也就大大降低,从而进一步增加消耗。
-
-
-
留意从FrameBuffer中读取数据可能造成的延迟,如Occlusion Query。
-
HSR Enable的硬件,不需要对三角面片进行手动排序
6.参考文章
[3] Understanding PowerVR Series5XT: PowerVR, TBDR and architecture efficiency
[4] POWERVR GRAPHICS ARCHITECTURES
[5] A look at the PowerVR graphics architecture: Tile-based rendering
[7] PowerVR Hardware Architecture Overview for Developers
[8] A look at the PowerVR graphics architecture: Deferred rendering
[10] Tile-based 和 Full-screen 方式的 Rasterization 相比有什么优劣? - xiaocai的回答 - 知乎