Android-View繪制原理(15)-RenderNodeDrawable

上一篇文章介紹了SkiaOpenGLPipeline.draw主流程暴心,其中renderFrame是一個主要的流程之一火架,本文將繼續(xù)去分析這個renderFrame方法缎患。這個方法是定義在SkiaOpenGLPipeline的父類SkiaPipeline上
frameworks/base/libs/hwui/pipeline/skia/SkiaPipeline.cpp

void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip,
                               const std::vector<sp<RenderNode>>& nodes, bool opaque,
                               const Rect& contentDrawBounds, sk_sp<SkSurface> surface,
                               const SkMatrix& preTransform) {
    ...
    SkCanvas* canvas = tryCapture(surface.get(), nodes[0].get(), layers);
    ...
    renderLayersImpl(layers, opaque);
    ...
    renderFrameImpl(clip, nodes, opaque, contentDrawBounds, canvas, preTransform);
    ...
}

它的流程還是非常清晰的惋增,先在SkSurface上創(chuàng)建一個canvas,然后先渲染Layer祥款,后渲染nodes清笨。 先看一下幾個參數的來源

  • layers。這個是在前面是遍歷RenderNode 樹形結構的時候刃跛,如果發(fā)現一些節(jié)點的layertype == RENDER_LAYER, 則為這些RenderNode生成一個Layer,每個Layer都有一個SkSurface.然后將這個layer加入到這個layers苛萎。如果沒有手動設置過layertype的話桨昙,layers是empty的。
  • nodes, 是CanvasContext的mRenderNodes腌歉,正常情況下只有一個元素,類型是RootRenderNode蛙酪。多元素的情況目前我還沒有發(fā)現。
  • surface 前面構建的基于SkGpuDevice的SkSurface對象

有了這些背景知識翘盖,我們看看上面方法內部的幾個方法

1 tryCapture

SkCanvas* SkiaPipeline::tryCapture(SkSurface* surface, RenderNode* root,
    const LayerUpdateQueue& dirtyLayers) {
    if (CC_LIKELY(!Properties::skpCaptureEnabled)) {
        return surface->getCanvas(); // Bail out early when capture is not turned on.
    }
    ...
}

capture是一種debug的場景桂塞,正常情況下,直接就進入這個分支
external/skia/src/image/SkSurface.cpp

SkCanvas* SkSurface::getCanvas() {
    return asSB(this)->getCachedCanvas();
}

繼續(xù)調用asSB方法馍驯,希望這個名字不要給河蟹哈
**

static SkSurface_Base* asSB(SkSurface* surface) {
    return static_cast<SkSurface_Base*>(surface);
}

external/skia/src/image/SkSurface_Base.h

SkCanvas* SkSurface_Base::getCachedCanvas() {
    if (nullptr == fCachedCanvas) {
        fCachedCanvas = std::unique_ptr<SkCanvas>(this->onNewCanvas());
        if (fCachedCanvas) {
            fCachedCanvas->setSurfaceBase(this);
        }
    }
    return fCachedCanvas.get();
}

這里繼續(xù)調用onNewCanvas阁危,因此這個SkSuface實際類型是SkSurface_Gpu, 因為我們看看它的onNewCanvas方法
external/skia/src/image/SkSurface_Gpu.cpp

SkCanvas* SkSurface_Gpu::onNewCanvas() { return new SkCanvas(fDevice); }

這里直接以fDevice為參數創(chuàng)建一個新的SkCanvas。這在之前分析SkCanvas時說過汰瘫,創(chuàng)建一個依賴SkGpuDevice的SkCanvas來會繪制才能真正的去做像素渲染狂打。這里的fDevice就真是一個SkGpuDevice。因此這里生成的SkCanvas會真正的去調用GPU渲染像素混弥。

所以tryCapture方法就是準備一個真正的可以渲染像素的Canvas

2 renderLayersImpl

這個方法先去處理layers趴乡。因此我們先來分析一下layer是如何處理的,它將涉及到本人的主角RenderNodeDrawable蝗拿,它繼承自SkDrawable

frameworks/base/libs/hwui/pipeline/skia/RenderNodeDrawable.h

class RenderNodeDrawable : public SkDrawable {}
void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) {
    sk_sp<GrDirectContext> cachedContext;

    for (size_t i = 0; i < layers.entries().size(); i++) {
        RenderNode* layerNode = layers.entries()[i].renderNode.get();
        ...
        SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas();
        ...
        RenderNodeDrawable root(layerNode, layerCanvas, false);
        root.forceDraw(layerCanvas);
        layerCanvas->restoreToCount(saveCount);
        ...
        GrDirectContext* currentContext =
            GrAsDirectContext(layerNode->getLayerSurface()->getCanvas()->recordingContext());
        if (cachedContext.get() != currentContext) {
            if (cachedContext.get()) {
                ATRACE_NAME("flush layers (context changed)");
                cachedContext->flushAndSubmit();
            }
            cachedContext.reset(SkSafeRef(currentContext));
        }
    }

    if (cachedContext.get()) {
        ATRACE_NAME("flush layers");
        cachedContext->flushAndSubmit();
    }
}

renderLayersImpl方法會遍歷所有的layers晾捏,然后針對每個layer,進行一些列的判斷哀托,滿足某些條件的layer才會執(zhí)行渲染惦辛。這里的條件包括,如對一個的RenderNode的SkSurface()不為空萤捆,layer對于的RenderNode有繪制指令等裙品。layerNode->getLayerSurface()->getCanvas()這里返回的 SkCanvas也即使SkGpuDevice的canvas俗批。然后構造一個RenderNodeDrawable對象,然后調用forceDraw市怎,就進入到RenderNodeDrawable的邏輯

RenderNodeDrawable::RenderNodeDrawable(RenderNode* node, SkCanvas* canvas, bool composeLayer,
                                       bool inReorderingSection)
        : mRenderNode(node)
        , mRecordedTransform(canvas->getTotalMatrix())
        , mComposeLayer(composeLayer)
        , mInReorderingSection(inReorderingSection) {}

這里mComposeLayer將給賦值為傳入的是false岁忘。

void RenderNodeDrawable::forceDraw(SkCanvas* canvas) const {
    RenderNode* renderNode = mRenderNode.get();
    MarkDraw _marker{*canvas, *renderNode};

    if ((mProjectedDisplayList == nullptr && !renderNode->isRenderable()) ||
        (renderNode->nothingToDraw() && mComposeLayer)) {
        return;
    }

    SkiaDisplayList* displayList = renderNode->getDisplayList().asSkiaDl();

    SkAutoCanvasRestore acr(canvas, true);
    const RenderProperties& properties = this->getNodeProperties();
    // pass this outline to the children that may clip backward projected nodes
    displayList->mProjectedOutline =
            displayList->containsProjectionReceiver() ? &properties.getOutline() : nullptr;
    if (!properties.getProjectBackwards()) {
        drawContent(canvas);
        if (mProjectedDisplayList) {
            acr.restore();  // draw projected children using parent matrix
            LOG_ALWAYS_FATAL_IF(!mProjectedDisplayList->mProjectedOutline);
            const bool shouldClip = mProjectedDisplayList->mProjectedOutline->getPath();
            SkAutoCanvasRestore acr2(canvas, shouldClip);
            canvas->setMatrix(mProjectedDisplayList->mParentMatrix);
            if (shouldClip) {
                canvas->clipPath(*mProjectedDisplayList->mProjectedOutline->getPath());
            }
            drawBackwardsProjectedNodes(canvas, *mProjectedDisplayList);
        }
    }
    displayList->mProjectedOutline = nullptr;
}

在繪制layer的時候,會判斷是否需要繪制区匠,如果不可繪制或者沒有繪制內容且composeLayer = true則不需要繪制干像,之后會取出RenderNode 的properties,如果不是getProjectBackwards的話驰弄,才進行繪制麻汰,因為設置為ProjectBackwards的節(jié)點會被繪制到它的錨點的節(jié)點里。進入之后會先調用drawContent(canvas);繪制內容戚篙,然后在判斷mProjectedDisplayList是否為空五鲫,如果不為空的話,表示它就是一個投影錨點岔擂,需要去繪制被投影的那些節(jié)點位喂,那些節(jié)點的繪制指令就保存在mProjectedDisplayList里面。

void RenderNodeDrawable::drawContent(SkCanvas* canvas) const {
    RenderNode* renderNode = mRenderNode.get();
    
    SkiaDisplayList* displayList = mRenderNode->getDisplayList().asSkiaDl();
    displayList->mParentMatrix = canvas->getTotalMatrix();
        SkiaDisplayList* displayList = renderNode->getDisplayList().asSkiaDl();
       
        if (renderNode->getLayerSurface() && mComposeLayer) {
             sk_sp<SkImage> snapshotImage  = renderNode->getLayerSurface()->makeImageSnapshot();
            if (stretch.isEmpty() ||
                Properties::getStretchEffectBehavior() == StretchEffectBehavior::UniformScale) {
                ...
                if (renderNode->hasHolePunches()) {
                    TransformCanvas transformCanvas(canvas, SkBlendMode::kClear);
                    displayList->draw(&transformCanvas);
                }
                canvas->drawImageRect(snapshotImage, SkRect::Make(srcBounds),
                                      SkRect::Make(dstBounds), sampling, &paint,
                                      SkCanvas::kStrict_SrcRectConstraint);
            } 
            ...
        } else {
            if (alphaMultiplier < 1.0f) {
                // Non-layer draw for a view with getHasOverlappingRendering=false, will apply
                // the alpha to the paint of each nested draw.
                AlphaFilterCanvas alphaCanvas(canvas, alphaMultiplier);
                displayList->draw(&alphaCanvas);
            } else {
                displayList->draw(canvas);
            }
        }
    }
}

如果是mComposeLayer的layer且存在SkSurface乱灵,如果不是打孔屏幕的話塑崖,會獲取SKSurface中的緩存SkImage,然后將這個SkImage畫到SkCavas中痛倚,從而不會再執(zhí)行它的DisplayList的指令规婆;否則則執(zhí)行displayList中的指令,將displayList畫到canvas蝉稳。但是如果是打孔屏幕的畫抒蚜,還是要重新繪制一遍displayList,似乎打孔屏幕不能利用到到Layer緩存帶來的性能由優(yōu)化颠区,只是是因為使用的是TransformCanvas包裝了canvas削锰,它會過濾掉一些指令,因此不會執(zhí)行所有的指令毕莱。

displayList->draw(canvas);
diaplayList的類型是SkisDisplayList器贩,它里面保存的是錄制的繪制指令
frameworks/base/libs/hwui/pipeline/skia/SkiaDisplayList.h

void draw(SkCanvas* canvas) { mDisplayList.draw(canvas); }

mDisplayList的類型是DisplayListData,定義RecordingCanvas

void DisplayListData::draw(SkCanvas* canvas) const {
    SkAutoCanvasRestore acr(canvas, false);
    this->map(draw_fns, canvas, canvas->getTotalMatrix());
}

關于draw_fns的定義如下:

#define X(T)                                                    \
    [](const void* op, SkCanvas* c, const SkMatrix& original) { \
        ((const T*)op)->draw(c, original);                      \
    },
static const draw_fn draw_fns[] = {
#include "DisplayListOps.in"
};
#undef X

DisplayListOps.in的內容如下:
frameworks/base/libs/hwui/DisplayListOps.in

X(Flush)
X(Save)
....
X(DrawRect)
...

這里是通過宏定義了一些lamda用于的draw方法朋截。以此Flush蛹稍,Save,DrawRect部服,為例子唆姐,將draw_fns展開為如下的內容:

static const draw_fn draw_fns[] = {
    [](const void* op, SkCanvas* c, const SkMatrix& original) { 
    ((const Flush*)op)->draw(c, original); 
    },      
     [](const void* op, SkCanvas* c, const SkMatrix& original) { 
    ((const Save*)op)->draw(c, original); 
    },      
   [](const void* op, SkCanvas* c, const SkMatrix& original) { 
    ((const DrawRect*)op)->draw(c, original); 
    },      
}

map方法如下:

template <typename Fn, typename... Args>
inline void DisplayListData::map(const Fn fns[], Args... args) const {
    auto end = fBytes.get() + fUsed;
    for (const uint8_t* ptr = fBytes.get(); ptr < end;) {
        auto op = (const Op*)ptr;
        auto type = op->type;
        auto skip = op->skip;
        if (auto fn = fns[type]) {  // We replace no-op functions with nullptrs
            fn(op, args...);        // to avoid the overhead of a pointless call.
        }
        ptr += skip;
    }
}

前面介紹過fBytes就是存儲繪制Op的數據塊,map函數遍歷取出這個op之后調用對應的lamda進行處理
每一個op的有他自己的type和draw方法廓八。比如DrawRect

    struct DrawRect final : Op {
         static const auto kType = Type::DrawRect;
         DrawRect(const SkRect& rect, const SkPaint& paint) : rect(rect), paint(paint) {}
         SkRect rect;
         SkPaint paint;
         void draw(SkCanvas* c, const SkMatrix&) const { c->drawRect(rect, paint); }
};
    

Type::DrawRect也是有一個宏定義奉芦,它也使用相同的“DisplayListOps.in”赵抢,所以每個繪制Op的都能以它的type作為下標找到對應lamda處理函數

   #define X(T) T,
   enum class Type : uint8_t {
           #include "DisplayListOps.in"
   };
   #undef X

以DrawRect為例fn(op, args...); 即調了DrawRect的draw方法。最后即調用到SkCanvas的drawRect方法,這個方法再介紹SkCanvas的時候已經介紹了声功,因此這里就不再介紹了烦却。

遍歷完整個fBytes之后,所有的之前錄制(繪制)到DisplayList內容就保存到了SkCpuDevice的GrSurfaceDrawContextget的GrOpsTask里面了先巴。但仍還沒有提交到GPU其爵。

當所有的Layer的渲染完了之后,會調flushAndSubmit來提交GPU伸蚯,于是完成渲染摩渺。

 if (cachedContext.get()) {
        ATRACE_NAME("flush layers");
        cachedContext->flushAndSubmit();
    }

3 renderFrameImpl

這個邏輯和renderLayer差不多

void SkiaPipeline::renderFrameImpl(const SkRect& clip,
                                   const std::vector<sp<RenderNode>>& nodes, bool opaque,
                                   const Rect& contentDrawBounds, SkCanvas* canvas,
                                   const SkMatrix& preTransform) {
    ...
    if (1 == nodes.size()) {
        if (!nodes[0]->nothingToDraw()) {
            RenderNodeDrawable root(nodes[0].get(), canvas);
            root.draw(canvas);
        }
    } else if (0 == nodes.size()) {
        // nothing to draw
    } else {
       ...
    }

因為i大部分情況下nodes的元素為1個,因此直接就將他轉換成一個RenderNodeDrawable剂邮,但是只調用的是draw方法摇幻,而不是forceDraw方法。RenderNodeDrawable構造方法默認的composeLayer是true抗斤。

frameworks/base/libs/hwui/pipeline/skia/RenderNodeDrawable.h

class RenderNodeDrawable : public SkDrawable {
      explicit RenderNodeDrawable(RenderNode* node, SkCanvas* canvas, bool composeLayer = true,
                                bool inReorderingSection = false);
      ...
}                                

它的draw方法定義再父類SkDrawable中

external/skia/include/core/SkDrawable.h

void draw(SkCanvas*, const SkMatrix* = nullptr);

external/skia/src/core/SkDrawable.cpp

void SkDrawable::draw(SkCanvas* canvas, const SkMatrix* matrix) {
    SkAutoCanvasRestore acr(canvas, true);
    if (matrix) {
        canvas->concat(*matrix);
    }
    this->onDraw(canvas);

    if (false) {
        draw_bbox(canvas, this->getBounds());
    }
}

于是回調子類實現的onDraw方法

void RenderNodeDrawable::onDraw(SkCanvas* canvas) {
    // negative and positive Z order are drawn out of order, if this render node drawable is in
    // a reordering section
    if ((!mInReorderingSection) || MathUtils::isZero(mRenderNode->properties().getZ())) {
        this->forceDraw(canvas);
    }
}

最后還是進入到forceDraw方法囚企,只是mComposeLayer = true,但是它的laysurface為null瑞眼,還是直接進入到這段邏輯

 if (alphaMultiplier < 1.0f) {
                // Non-layer draw for a view with getHasOverlappingRendering=false, will apply
                // the alpha to the paint of each nested draw.
                AlphaFilterCanvas alphaCanvas(canvas, alphaMultiplier);
                displayList->draw(&alphaCanvas);
            } else {
                displayList->draw(canvas);
            }

最后仍然走到displayList->draw(canvas);
這里需要注意的是,再ViewGroup中棵逊,繪制子控件的時候伤疙,會調用一個drawRenderNode,將子控件的RenderNode轉換成一個RendeNodeDrawable辆影,然后使用DrawDrawble指令寫入到父控件的fBytes,因此在循環(huán)從fBytes中讀取出來的Op中可能包含DrawDrawable徒像,這樣的話,就會進行遞歸的調用RendeNodeDrawable.draw方法了蛙讥。

4 總結

本文主要分析了renderFrame函數的原理锯蛀,包括了對Layer的處理和RootRenderNode的處理,他們最后都是通過RenderNodeDrawable來進行渲染的次慢。然后將RootRenderNode中的displayList畫到SkSurface中完成像素渲染旁涤。其中對于Layer的處理邏輯比較難理解。我總結一下設置成Layer與不設置成Layer的差別

  1. 設置成LAYER_TYPE_HARDWARE的View迫像,在prepareTree的時候會為dirty的RenderNode創(chuàng)建一個SkSurface劈愚,并且保存到layers中去
  2. 渲染的時候,會先去渲染這些layer闻妓,因此傳入mComposeLayer為false菌羽,因此會執(zhí)行RenderNode的displayList繪制,并繪制到layer自己的的SkSurface中去
  3. 渲染幀的時候由缆,是使用的RootRenderNode注祖,它的displayList的fBytes中DrawDrawable類型的Op仍然持有上面那些設置成layer的RenderNode猾蒂,但是因為displayList中的RendeNodeDrawable都是設置mComposeLayer = true,因此在RendeNodeDrawable繪制的時候是晨,如果遇到layer類型的RendeNode則利用第二步中畫好的SkSurface生成一個SkImage肚菠,再將SkImage畫到 最終的canvas中去。
  4. Layer創(chuàng)建好后署鸡,如果沒有發(fā)生變化案糙,則不會設置成layer的RenderNode創(chuàng)建新的layer,也不會出現再layers里面靴庆,但它持有的原來的layer时捌,因此再繪制幀的時候直接進入第3步,從而得到優(yōu)化炉抒。
  5. Layer除了能內容沒有發(fā)生變化的時候奢讨,可以重用之前的繪制的SkImage外,也可以作為一個整體應用某些屬性焰薄。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末拿诸,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子塞茅,更是在濱河造成了極大的恐慌亩码,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件野瘦,死亡現場離奇詭異描沟,居然都是意外死亡,警方通過查閱死者的電腦和手機鞭光,發(fā)現死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門吏廉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人惰许,你說我怎么就攤上這事席覆。” “怎么了汹买?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵佩伤,是天一觀的道長。 經常有香客問我卦睹,道長畦戒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任结序,我火速辦了婚禮障斋,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己垃环,他們只是感情好邀层,可當我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著遂庄,像睡著了一般寥院。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上涛目,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天秸谢,我揣著相機與錄音,去河邊找鬼霹肝。 笑死估蹄,一個胖子當著我的面吹牛,可吹牛的內容都是我干的沫换。 我是一名探鬼主播臭蚁,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼讯赏!你這毒婦竟也來了垮兑?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤漱挎,失蹤者是張志新(化名)和其女友劉穎系枪,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體磕谅,經...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡嗤无,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了怜庸。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡垢村,死狀恐怖割疾,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情嘉栓,我是刑警寧澤宏榕,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站侵佃,受9級特大地震影響麻昼,放射性物質發(fā)生泄漏。R本人自食惡果不足惜馋辈,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一抚芦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦叉抡、人聲如沸尔崔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽季春。三九已至,卻和暖如春消返,著一層夾襖步出監(jiān)牢的瞬間载弄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工撵颊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留宇攻,地道東北人。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓秦驯,卻偏偏與公主長得像尺碰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子译隘,可洞房花燭夜當晚...
    茶點故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內容