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框架的其他改进。
Comments