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

D3D11.1和OpenGL ES都提供了discard frame buffer的方法,分别成为DiscardView和GL_EXT_discard_frame_buffer。虽有文档,但却很少有例子来表达这功能在什么情况下使用。这里我只能根据我的理解做一些探讨。希望有更了解的朋友可以一起讨论。

从tile-based说起

移动平台上最常见的GPU架构是tiled-based。它把frame buffer划分成许多大小相同的tile。每个rasterizer一次处理一个tile,把这个tile中包含的三角形光栅化到那个区域。为了提高性能、减少功耗,tile-based硬件会有一个片上空间,真正的光栅化仅仅发生在这个片上空间,当一个tile的所有三角形都渲染完成之后,才写入位于video memory的frame buffer。可以认为,那个片上空间是个cache。需要注意的是,从video memory载入cache和从cache写到video memory都是以tile为单位的。要么整个tile读写,要么都不读写。不能只读写其中某些pixel。这个cache把multisample、alpha blending等操作都在片内完成,速度明显远远高于每次都和video memory打交道的做法。

BindFramebuffer的时候发生了什么

很多人都应该遇到过在BindFramebuffer/SetRenderTarget的时候突然变慢的情况,尤其是tile-based GPU上。从前面tiled-based的做法可以看出,在frame buffer切换的时候,需要做3件事情:

  1. 把当前cache的内容写入frame buffer,否则上次就白渲了。这个过程称为resolve。即便只有一个pixel被渲染,也要把整个tile都resolve。
  2. 设置frame buffer指针。
  3. 把新frame buffer的内容载入cache。这个过程称为restore。即便只有一个pixel将被覆盖,也需要把整个tile都restore。

由此可见,切换frame buffer的主要开销来自于resolve和restore。这两个东西都需要和video memory大量交换数据,造成了性能骤减。

如何减少开销

tile-based的文档一般都会讲到为了提高BindFramebuffer的性能,在bind之后应该紧接着Clear一下。这能提示驱动那个frame buffer的内容已经不要了,这能避免restore的操作,让带宽开销减半。在这个时候,也可以通过Discard来提示驱动,其他相同的作用。

Bringing AAA graphics to mobile platforms中提到了Discard的另一个作用。OpenGLES要求如果frame buffer带有depth buffer,就必须也有一个color buffer。所以在生成shadow map的时候,除了必要的depth之外,color也会不必要的经历resolve和restore。通过在生成shadow map之前Clear或Discard可以去掉restore。同样,生成shadow map之后调用一下Discard,更可以去掉resolve。这时候再把color write关掉,就连cache都不需要写入,使得color buffer的存在几乎不产生额外开销。

传统GPU

在传统的非tile-based的GPU上,也有类似的cache用来加速渲染。同时,因为texture在GPU上就是分tile存储的,所以也很可能会涉及到以tile为单位的resolve和restore。所以D3D11.1上新增的Discard适用于各种平台。在KlayGE的开发版本里,我已经在可能的地方加上了Discard。虽然和Clear比起来没有明显提升,但相信对有些平台有好处。

另外,D3D11.1的debug layer在这上面还提供了帮助。如果在不该用Discard的地方误用了Discard,debug layer会把它填充成一个随机的颜色,使得在屏幕上一下就能看出来。

总结和问题

Discard的能力已经比较明确,尤其是在移动平台,能极大提高frame buffer切换的效率。但在防止resolve方面,这里我仍有些疑问,为何关掉color write的限定还不够强烈,以至于驱动需要Discard的提示?另外,除了shadow map,Discard还有哪些具体场合应该使用?