上一篇文章介紹了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。 先看一下幾個參數(shù)的來源
- layers。這個是在前面是遍歷RenderNode 樹形結(jié)構(gòu)的時候,如果發(fā)現(xiàn)一些節(jié)點的layertype == RENDER_LAYER, 則為這些RenderNode生成一個Layer,每個Layer都有一個SkSurface.然后將這個layer加入到這個layers。如果沒有手動設(shè)置過layertype的話,layers是empty的。
- nodes, 是CanvasContext的mRenderNodes,正常情況下只有一個元素,類型是RootRenderNode。多元素的情況目前我還沒有發(fā)現(xiàn)。
- surface 前面構(gòu)建的基于SkGpuDevice的SkSurface對象
有了這些背景知識,我們看看上面方法內(nèi)部的幾個方法
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ù)調(diào)用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ù)調(diào)用onNewCanvas,因此這個SkSuface實際類型是SkSurface_Gpu, 因為我們看看它的onNewCanvas方法
external/skia/src/image/SkSurface_Gpu.cpp
SkCanvas* SkSurface_Gpu::onNewCanvas() { return new SkCanvas(fDevice); }
這里直接以fDevice為參數(shù)創(chuàng)建一個新的SkCanvas。這在之前分析SkCanvas時說過,創(chuàng)建一個依賴SkGpuDevice的SkCanvas來會繪制才能真正的去做像素渲染。這里的fDevice就真是一個SkGpuDevice。因此這里生成的SkCanvas會真正的去調(diào)用GPU渲染像素。
所以tryCapture方法就是準(zhǔn)備一個真正的可以渲染像素的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。然后構(gòu)造一個RenderNodeDrawable對象,然后調(diào)用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的時候,會判斷是否需要繪制,如果不可繪制或者沒有繪制內(nèi)容且composeLayer = true則不需要繪制,之后會取出RenderNode 的properties,如果不是getProjectBackwards的話,才進行繪制,因為設(shè)置為ProjectBackwards的節(jié)點會被繪制到它的錨點的節(jié)點里。進入之后會先調(diào)用drawContent(canvas);繪制內(nèi)容,然后在判斷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());
}
關(guān)于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的內(nèi)容如下:
frameworks/base/libs/hwui/DisplayListOps.in
X(Flush)
X(Save)
....
X(DrawRect)
...
這里是通過宏定義了一些lamda用于的draw方法。以此Flush,Save,DrawRect,為例子,將draw_fns展開為如下的內(nèi)容:
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的數(shù)據(jù)塊,map函數(shù)遍歷取出這個op之后調(diào)用對應(yīng)的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作為下標(biāo)找到對應(yīng)lamda處理函數(shù)
#define X(T) T,
enum class Type : uint8_t {
#include "DisplayListOps.in"
};
#undef X
以DrawRect為例fn(op, args...); 即調(diào)了DrawRect的draw方法。最后即調(diào)用到SkCanvas的drawRect方法,這個方法再介紹SkCanvas的時候已經(jīng)介紹了,因此這里就不再介紹了。
遍歷完整個fBytes之后,所有的之前錄制(繪制)到DisplayList內(nèi)容就保存到了SkCpuDevice的GrSurfaceDrawContextget的GrOpsTask里面了。但仍還沒有提交到GPU。
當(dāng)所有的Layer的渲染完了之后,會調(diào)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個,因此直接就將他轉(zhuǎn)換成一個RenderNodeDrawable,但是只調(diào)用的是draw方法,而不是forceDraw方法。RenderNodeDrawable構(gòu)造方法默認的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());
}
}
于是回調(diào)子類實現(xiàn)的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中,繪制子控件的時候,會調(diào)用一個drawRenderNode,將子控件的RenderNode轉(zhuǎn)換成一個RendeNodeDrawable,然后使用DrawDrawble指令寫入到父控件的fBytes,因此在循環(huán)從fBytes中讀取出來的Op中可能包含DrawDrawable,這樣的話,就會進行遞歸的調(diào)用RendeNodeDrawable.draw方法了。
4 總結(jié)
本文主要分析了renderFrame函數(shù)的原理,包括了對Layer的處理和RootRenderNode的處理,他們最后都是通過RenderNodeDrawable來進行渲染的。然后將RootRenderNode中的displayList畫到SkSurface中完成像素渲染。其中對于Layer的處理邏輯比較難理解。我總結(jié)一下設(shè)置成Layer與不設(shè)置成Layer的差別
- 設(shè)置成LAYER_TYPE_HARDWARE的View,在prepareTree的時候會為dirty的RenderNode創(chuàng)建一個SkSurface,并且保存到layers中去
- 渲染的時候,會先去渲染這些layer,因此傳入mComposeLayer為false,因此會執(zhí)行RenderNode的displayList繪制,并繪制到layer自己的的SkSurface中去
- 渲染幀的時候,是使用的RootRenderNode,它的displayList的fBytes中DrawDrawable類型的Op仍然持有上面那些設(shè)置成layer的RenderNode,但是因為displayList中的RendeNodeDrawable都是設(shè)置mComposeLayer = true,因此在RendeNodeDrawable繪制的時候,如果遇到layer類型的RendeNode則利用第二步中畫好的SkSurface生成一個SkImage,再將SkImage畫到 最終的canvas中去。
- Layer創(chuàng)建好后,如果沒有發(fā)生變化,則不會設(shè)置成layer的RenderNode創(chuàng)建新的layer,也不會出現(xiàn)再layers里面,但它持有的原來的layer,因此再繪制幀的時候直接進入第3步,從而得到優(yōu)化。
- Layer除了能內(nèi)容沒有發(fā)生變化的時候,可以重用之前的繪制的SkImage外,也可以作為一個整體應(yīng)用某些屬性。