白鷺引擎渲染優化 - CacheAsBitmap

CacheAsBitmap

這篇文章要從 egret 中的對象基類 DisplayObject 實例屬性 cacheAsBitmap 提及。官方文檔建議靜態的UI使用建議設置 cacheAsBitmap 爲 true 減小重繪次數。node

若是設置爲 true,則 Egret 運行時將緩存顯示對象的內部位圖表示形式。此緩存能夠提升包含複雜矢量內容的顯示對象的性能。web

  • 將 cacheAsBitmap 屬性設置爲 true 後,呈現並不更改,可是,顯示對象將自動執行像素貼緊。執行速度可能會大大加快,
  • 具體取決於顯示對象內容的複雜性。最好將 cacheAsBitmap 屬性與主要具備靜態內容且不頻繁縮放或旋轉的顯示對象一塊兒使用。

爲了一探究竟 cacheAsBitmap 是如何緩存和減小重繪次數,簡單分析下源碼。canvas

// 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,到這裏就再也不產生渲染次數了。webgl

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只對子節點進行一次渲染。ui

小結

從源碼分析了爲何 cacheAsBitMap 設置爲 true 時能夠減小ui的渲染次數,每次render經過減小子節點的渲染。所以將動靜態節點分層而後在靜態層設置 cacheAsBitMap 可以有效減小渲染次數。this

相關文章
相關標籤/搜索