转载请注明出处为KlayGE游戏引擎,本文的永久链接为http://www.klayge.org/?p=2870

KlayGE 4.4中的Tile-based Deferred Rendering(TBDR)是个基于pixel shader的算法,每个batch最多32个光源。这个方法适合于不支持compute shader的设备。因为在compute shader里,可以利用group memory把更多光源打包到同一个batch里,进一步提高效率并减少带宽。

改进的算法

CS可以通过group memory在thread之间共享数据,只要利用得好这部分,就能极大提升性能。在TBDR中,group memory主要用在两部分。第一部分是tiling阶段,用的是常见的reduce方法。先让所有thread读满32×32个depth,reduce得到1×1,再写入目标纹理。

第二部分是shading阶段。每个group会对所有光源做一次求交测试,把有效的光源写入group memory。接着在同一个shader内遍历这个有效光源的列表就可以计算shading,不需要其他video memory的IO。

一个细节上的不同在于,PS版本需要根据光源类型分成多个shader,每个pass一种光源,性能最高。CS版本则是把所有光源类型都写到一个shader里,一次完成,性能最高。

带宽比较

同样,这里需要比较一下CS版本和PS版本的带宽。假设G-Buffer由2个ABGR8组成,depth buffer的格式是float,光源数量是L、分为N个一组。Shading buffer的格式是B10G11R11F。那么可以比较一下平均一个pixel的带宽。

PS TBDR CS TBDR
Tiling读取带宽 (32*32 + 2*(16*16 + 8*8 + 4*4 + 2*2)) / (32*32) * 4 = 6.66 (32*32) / (32*32) * 4 = 4
Tiling写入带宽 2*(16*16 + 8*8 + 4*4 + 2*2 + 1*1) / (32*32) * 4 = 2.66 2*(1*1) / (32*32) * 4 = 0.0078
Shading读取带宽 L/N * (4 + 4) + L/N * 4 = L/N * 12 L/N * (4 + 4) + L/N * 4 = L/N * 12
Shading写入带宽 L/N * 4 L/N * 4
总和 L/N*16+9.32 L/N*16+4.0078

在tiling的阶段,PS版本需要通过每次减半,多个pass才能得到tile的最大最小深度。而CS版本只需要读取一次,在shader里累加就能算出最终结果。所以这部分带宽能省一半。在lighting/shading的时候,两者算法和带宽消耗的计算公式相同。但PS版本的N最大只有32,CS版本的可以到1024。所以同样光源数的情况下,CS版本只需要PS版本1/32的带宽。

结果

在实现完成,测试性能的时候,我担心的事情还是发生了。CS的自由度远大于PS,所以驱动对其的优化也比较困难。一般来说,做同样的事情,PS比CS快。而且每个thread占用很多group memory,使得GPU的并行能力无法发挥到极限。对于TBDR来说,虽然一个pass里CS版本比PS版本能多处理32倍的光源数,但性能提升则不大。对于Fermi架构的GPU来说,大概是20%。对于Kepler几乎没有提升了。看来还需要在计算和并行度上继续优化。

本篇讲了一个CS版本的TBDR,下一篇会将一些Deferred框架的其他改进。