内容渲染速度是决定一个UI成败的关键。无论UI做得多华丽,没有速度都没有意义。
在MFC,WTL等开发框架下,每个控件都是一个窗口,窗口只需要画前景,背景。因为窗口之间的内容不需要做混合,一个子窗口的一次刷新只涉及该窗口本身,和其它窗口无关,因此这样效率很高。但是美中不足在于,窗口之间内容是孤立的,要想不同窗口之间的内容更协调,对美术有很高的要求,同时也基本只能适应个别窗口大小相对固定的场景。
要使UI更加漂亮,最简单有效的方法就是采用alpha混合的方式将各层的窗口相互混合,这样窗口之间没有了边界的感觉,从而使得UI更像一个整体,这也是现代UI的主要实现方式。
想像一下,如果一个最上层的子窗口内容发生了变化,UI系统如何把窗口内容绘制出来呢?
首先需要通知UI系统的渲染对象说我的窗口显示区域内容发生了变化,请求重绘。
UI系统拿到渲染对象后,对渲染范围设定剪裁区(clip),防止刷新那些不需要重绘的区域。
UI系统从最顶层的窗口开始依次绘制每一层的和剪裁区重叠的窗口的内容(不同窗口之间绘制内容的混合由绘制方法定义,例如图片可能是利用alpha通道)。
为了防止绘制过程中出现闪烁,一般上面绘制都是在一个内存绘图设备(如内存DC)上进行。在全部绘制完成后,将绘制内容再呈现到屏幕。
那么问题来了:一般子窗口会比父窗口小,子窗口请求重绘时,要执行父窗口的绘制函数,如果父窗口没有根据当前的剪裁范围来确定自己的绘制范围,那么尽管有剪裁区限制了不会绘制超出边界,在实际执行过程中,超出剪裁区的绘制行为也可能极大的影响UI的性能。
如何解决这个问题呢?
解决的方法可以有很多,比如在每个窗口的绘制方法都实现只绘制在剪裁区的那部分(实际上实现起来非常麻烦,附加的剪裁区计算也可能成为新的绘制瓶颈);又比如每个窗口都保留背景窗口的内容,当窗口需要刷新时,直接直接把背景内容拿过来在上面画。
第二种方法看起来效率是最高的,但是实际上可能并不高:虽然在绘制自己的内容时速度快了,问题是自己更新以后,还需要同时更新它的上层窗口的缓存,可能导致内存开销大,效率也不高。
在SOUI中,我们采用了第三种方法:给窗口加一个cache属性。
当cache=0时,窗口的绘制和前面提到的流程一样,只是如果窗口比较大,而需要一时半会的区域比较小时,速度慢一点。
如果cache=1,则窗口绘制的内容会被保存到一张缓存位图上,当其它窗口刷新并导致该窗口重绘时,直接从缓存中复制出来就可以了。
假定一个窗口使用一张小图片进行九宫格拉伸绘制出来,绘制整个窗口由于拉伸中的插值等一系列的计算,绘制可能需要消耗大量的CPU时间,而有了缓存以后,只在第一次绘制的时候使用图片绘制方法进行绘制,其它时间都是直接从缓存复制,效率能够极大的提高。
很显然,cache属性在提高渲染效率的同时,也需要开辟一块缓存来保存绘制内容,也就是常见的空间换时间的方法。因此如果绘制本身速度就足够快,那么完全可以不使用缓存(比如给一个窗口填充一个纯色的矩形)。
总结:
cache可以提高绘制速度,但是是以更高的内存占用为代价的,应该只在需要的地方使用(例如顶层背景窗口)。