原文地址:Killing Pixels - A New Optimization for Shading on ARM Mali GPUs
发帖人 Sean Ellis 于 2013 年 8 月 8 日 21:46:16 在 ARM Mali Graphics
给像素着色开销巨大,所以你一定不想花费时间和精力去给屏幕上那些不会真正出现的像素着色。为解决这一问题,ARM® Mali™ GPU 开拓了一种全新的优化方式。
但是,在介绍解决方案之前,首先我们来了解为何会出现不可见像素?在探索像素可见与不可见之时,您或许也想阅读一下Ed Plowman 的“关于原理以及像素在与不在”.
屏幕上各个像素的颜色由着色器程序决定。每个对象通常有不同的程序与之关联,对象中的每个像素都会衍生一个执行线程。这些线程一旦启动,就必定要完成(除非它们执行一个“丢弃”指令而自我终止),然后它们将计算得出的颜色传递到混合单元,在那里与输出图像中的现有像素值合并。
此处的关键问题是过度绘制 - 较近的对象绘制在较远对象上,而将它们隐藏。在地平线上绘制翡翠之城时,如果前景中有一座山遮挡(隐藏)该城的话,那么就无需细致地绘制该城。如果在发现过度绘制之前已经花费时间和精力渲染了翡翠像素,那就浪费了性能、时间和电池续航时间,也可能浪费了气氛。
目前已有几种旨在减少过度绘制像素开销的方式。第一种是让应用程序使用其对场景的了解来避免将几何体发送至图形驱动程序。这对以室内为基础的封闭式游戏效果不错,但要求游戏引擎中具备额外的逻辑。对于常见的场景类别,确定哪些对象会遮挡住别的对象实际上也比较困难。
即使您消除了一些距离较远的几何体,在一些情形中依然会有一些绘制出的几何体处于隐藏状态。或许同一房间中有敌方玩家 – 您可以看到钢盔,但其余部分隐藏在木箱后面。这时只需为其帽子顶部着色就已足够,而不必为整个人物着色像素。
借助一个简单的深度缓存,加上“早期”深度测试,就可以在开始着色像素之前,确定较远对象的像素会被较近对象的像素隐藏。
按照距离排列对象,再首先绘制最近的对象,可能有助于完成处理过程并消除过度绘制图像中的大多数隐藏像素。反过来做显然不行,因为管线没有超能力,无法了解后面要绘制的内容... 不过,先等一下。
但是,从前到后的排序方式存在一些其他问题。
对于半透明对象,从前到后绘制它们恰好是错误的顺序,因为它们需要与其后面的对象混合。再说,排序对象也需要时间。更为糟糕的是,现代图形API (OpenGL ES® 和Direct3D®)的结构其实并不包含“场景中对象”这一概念,所以你必须自行追踪并以可接受的顺序进行绘制。
另一种避免工作的方式是尽可能推迟着色:首先运行一个快速程序,仅计算深度并存储有关各个像素上哪一对象在前的数据;然后,在计算出所有像素后,再运行完整的照明计算。
这种方式效果非常好。至少它可以正常运作,直到您遇到打破规则的某些事物为止。或许是写入其自己深度的像素。又或是半透明对象。到那时,你必须回退到一个更加“蛮力”的运算模式,以便能跟踪额外的数据。这也不是软切换,因为一旦检测到特别情形性能就会显著下降,而且随着游戏引擎更加追求真实感,它们变得非常常见。
所以,在技术文章简介末尾难免要提出这样的问题:我们能够对此做些什么?
我们的答案是称为前像素终止 (FPK) 的一项专利技术,自Mali-T62X 和 T678 起的 ARM Mali GPU 中已包含这项技术,如最近推出的SamsungExynos5420。
在具备 FPK 功能的 GPU 中,为像素上色的线程并不是一旦启动就必定要完成。如果发现后续线程将向同一像素位置写入不透明数据,我们可以在任何时候终止已在进行的计算。由于完成每个线程的时间都是确定的,我们就有一个窗口期,可以用来消除管线中已存在的像素。实际上,我们利用管线的深度来模拟我早前提到的预见未来“超能力”。
事实上,它可以做的不只如此。通过向管线的起点添加一个简单的FIFO 缓冲 ,我们可以扩展前像素终止区域,使其更有可能发现过度绘制,同时让管线线有机会在线程启动之前将它们除掉。
对于 ARM Mali GPU 等基于区块的渲染器来说,这种方式效果尤其好。即便是最温和的消除区域,也可以产生与从前到后绘制顺序一样好的效果,但不需要对场景排序(造成硅面积、功耗和内存带宽的开销)。所以,无需修改您的应用程序来添加排序算法。另外,由于按照与自然顺序相同的次序进行绘制,半透明内容也显示正常,从而避免代价昂贵的变通办法造成性能降级。
而且,最好的方面在于操作规程之间的过渡是软性的 – 更像是匀速调节而非换挡。帧速率不一致(有时称为“闪烁”)对用户而言特别烦人,所以任何大幅降低场景渲染时间中不确定性的技巧都会受到用户和开发人员欢迎。