CacheAsBitmap
这篇文章要从 egret 中的对象基类 DisplayObject 实例属性 cacheAsBitmap 说起。官方文档建议静态的UI使用建议设置 cacheAsBitmap 为 true 减少重绘次数。
如果设置为 true,则 Egret 运行时将缓存显示对象的内部位图表示形式。此缓存可以提高包含复杂矢量内容的显示对象的性能。
- 将 cacheAsBitmap 属性设置为 true 后,呈现并不更改,但是,显示对象将自动执行像素贴紧。执行速度可能会大大加快,
- 具体取决于显示对象内容的复杂性。最好将 cacheAsBitmap 属性与主要具有静态内容且不频繁缩放或旋转的显示对象一起使用。
为了一探究竟 cacheAsBitmap 是如何缓存和减少重绘次数,简单分析下源码。
// class DisplayObject
public set cacheAsBitmap(value: boolean) {
……
self.$setHasDisplayList(value);
}
public $setHasDisplayList(value: boolean): void {
……
if (value) {
let displayList = sys.DisplayList.create(self);
if (displayList) {
this.$displayList = displayList;
this.$cacheDirty = true;
}
}
else {
this.$displayList = null;
}
}
可以看到设置 cacheAsBitmap 属性转化为设置 $displayList
,而这个属性值是 DisplayList 的实例,先不关心实例做了什么,等后续看到再展开。
DrawDisplayObject
找到了实际作用的属性 $displayList
,要在绘制图像方法中看这个属性到底起到了什么作用。我的办法比较笨,在全局搜 $displayList
属性,还好出现的文件不算多。在 class WebGLRenderer 中找到了绘制一个显示对象的方法 drawDisplayObject
。
简单来说 WebGLRenderer 这个类的作用就是当判断当前环境为 webgl 时使用的渲染类,跟它相对应的是 CanvasRenderer 类。
private drawDisplayObject(displayObject: DisplayObject, buffer: WebGLRenderBuffer, offsetX: number, offsetY: number, isStage?: boolean): number {
// drawcall 表示绘制次数
let drawCalls = 0;
// 表示当前渲染节点
let node: sys.RenderNode;
let displayList = displayObject.$displayList;
// isStage表示是否添加到舞台对象
if (displayList && !isStage) {
if (displayObject.$cacheDirty || displayObject.$renderDirty ||
displayList.$canvasScaleX != sys.DisplayList.$canvasScaleX ||
displayList.$canvasScaleY != sys.DisplayList.$canvasScaleY) {
// 当渲染节点比例发生变化时,需要绘制根节点显示对象到目标画布,返回draw的次数。
drawCalls += displayList.drawToSurface();
}
node = displayList.$renderNode;
}
else {
// $renderDirty为true表示更换了渲染节点,需要重新获取渲染节点和清空drawData数据
if (displayObject.$renderDirty) {
node = displayObject.$getRenderNode();
}
else {
node = displayObject.$renderNode;
}
}
……
isStage表示是否添加到舞台对象,根据文档,有且只有一个文档类容器会被添加到stage容器中
先来看 drawDisplayObject 前半部分的代码,可以看到设置了 cacheAsBitmap 与否决定了node 与 drawCall 的计算方式。
// drawDisplayObject方法
...
displayObject.$cacheDirty = false;
if (node) {
drawCalls++;
buffer.$offsetX = offsetX;
buffer.$offsetY = offsetY;
// 这里是根据node type来做一些渲染,不影响drawcall
switch (node.type) {
case sys.RenderNodeType.BitmapNode:
this.renderBitmap(<sys.BitmapNode>node, buffer);
break;
}
buffer.$offsetX = 0;
buffer.$offsetY = 0;
}
if (displayList && !isStage) {
return drawCalls;
}
...
这段代码中当渲染节点存在时增加一次 drawCall,然后如果 isStage 为false 且 cacheAsBitmap为true 函数就返回了,不进行后续的计算。于是全局搜了一下 drawDisplayObject 的调用,只有在主动调用render方法的时候 isStage 才为 true,意味着每一次render如果设置了cacheAsBitmap为true,到这里就不再产生渲染次数了。
// drawDisplayObject方法剩下的代码
let children = displayObject.$children;
if (children) {
if (displayObject.sortableChildren && displayObject.$sortDirty) {
//绘制排序
displayObject.sortChildren();
}
let length = children.length;
for (let i = 0; i < length; i++) {
let child = children[i];
let offsetX2;
let offsetY2;
let tempAlpha;
let tempTintColor;
if (child.$alpha != 1) {
tempAlpha = buffer.globalAlpha;
buffer.globalAlpha *= child.$alpha;
}
if (child.tint !== 0xFFFFFF) {
tempTintColor = buffer.globalTintColor;
buffer.globalTintColor = child.$tintRGB;
}
let savedMatrix: Matrix;
if (child.$useTranslate) {
let m = child.$getMatrix();
offsetX2 = offsetX + child.$x;
offsetY2 = offsetY + child.$y;
let m2 = buffer.globalMatrix;
savedMatrix = Matrix.create();
savedMatrix.a = m2.a;
savedMatrix.b = m2.b;
savedMatrix.c = m2.c;
savedMatrix.d = m2.d;
savedMatrix.tx = m2.tx;
savedMatrix.ty = m2.ty;
buffer.transform(m.a, m.b, m.c, m.d, offsetX2, offsetY2);
offsetX2 = -child.$anchorOffsetX;
offsetY2 = -child.$anchorOffsetY;
}
else {
offsetX2 = offsetX + child.$x - child.$anchorOffsetX;
offsetY2 = offsetY + child.$y - child.$anchorOffsetY;
}
switch (child.$renderMode) {
case RenderMode.NONE:
break;
case RenderMode.FILTER:
drawCalls += this.drawWithFilter(child, buffer, offsetX2, offsetY2);
break;
case RenderMode.CLIP:
drawCalls += this.drawWithClip(child, buffer, offsetX2, offsetY2);
break;
case RenderMode.SCROLLRECT:
drawCalls += this.drawWithScrollRect(child, buffer, offsetX2, offsetY2);
break;
default:
drawCalls += this.drawDisplayObject(child, buffer, offsetX2, offsetY2);
break;
}
if (tempAlpha) {
buffer.globalAlpha = tempAlpha;
}
if (tempTintColor) {
buffer.globalTintColor = tempTintColor;
}
if (savedMatrix) {
let m = buffer.globalMatrix;
m.a = savedMatrix.a;
m.b = savedMatrix.b;
m.c = savedMatrix.c;
m.d = savedMatrix.d;
m.tx = savedMatrix.tx;
m.ty = savedMatrix.ty;
Matrix.release(savedMatrix);
}
}
}
return drawCalls;
剩下的代码是对当前渲染节点的子节点进行渲染计算,也就是说,设置了 cacheAsBitMap 的 DisplayObject 每次render只对子节点进行一次渲染。
小结
从源码分析了为什么 cacheAsBitMap 设置为 true 时可以减少ui的渲染次数,每次render通过减少子节点的渲染。因此将动静态节点分层然后在静态层设置 cacheAsBitMap 能够有效减少渲染次数。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。