3

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容器中
image.png

先来看 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,到这里就不再产生渲染次数了。

image.png

// 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 能够有效减少渲染次数。


Obeing
665 声望108 粉丝

努力地成为一只小牛


引用和评论

0 条评论