Android View绘制原理 - RenderNodeDrawable
  mkIZDEUN7jdf 2023年11月02日 19 0

上一篇文章介绍了SkiaOpenGLPipeline.draw主流程,其中renderFrame是一个主要的流程之一,本文将继续去分析这个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上创建一个canvas,然后先渲染Layer,后渲染nodes。 先看一下几个参数的来源

  • layers。这个是在前面是遍历RenderNode 树形结构的时候,如果发现一些节点的layertype == RENDER_LAYER, 则为这些RenderNode生成一个Layer,每个Layer都有一个SkSurface.然后将这个layer加入到这个layers。如果没有手动设置过layertype的话,layers是empty的。
  • nodes, 是CanvasContext的mRenderNodes,正常情况下只有一个元素,类型是RootRenderNode。多元素的情况目前我还没有发现。
  • 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();
}

继续调用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();
}

这里继续调用onNewCanvas,因此这个SkSuface实际类型是SkSurface_Gpu, 因为我们看看它的onNewCanvas方法 external/skia/src/image/SkSurface_Gpu.cpp

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

这里直接以fDevice为参数创建一个新的SkCanvas。这在之前分析SkCanvas时说过,创建一个依赖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才会执行渲染。这里的条件包括,如对一个的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的节点会被绘制到它的锚点的节点里。进入之后会先调用drawContent(canvas);绘制内容,然后在判断mProjectedDisplayList是否为空,如果不为空的话,表示它就是一个投影锚点,需要去绘制被投影的那些节点,那些节点的绘制指令就保存在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中,从而不会再执行它的DisplayList的指令;否则则执行displayList中的指令,将displayList画到canvas。但是如果是打孔屏幕的画,还是要重新绘制一遍displayList,似乎打孔屏幕不能利用到到Layer缓存带来的性能由优化,只是是因为使用的是TransformCanvas包装了canvas,它会过滤掉一些指令,因此不会执行所有的指令。

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,因此在循环从fBytes中读取出来的Op中可能包含DrawDrawable,这样的话,就会进行递归的调用RendeNodeDrawable.draw方法了。

4 总结

本文主要分析了renderFrame函数的原理,包括了对Layer的处理和RootRenderNode的处理,他们最后都是通过RenderNodeDrawable来进行渲染的。然后将RootRenderNode中的displayList画到SkSurface中完成像素渲染。其中对于Layer的处理逻辑比较难理解。我总结一下设置成Layer与不设置成Layer的差别

  1. 设置成LAYER_TYPE_HARDWARE的View,在prepareTree的时候会为dirty的RenderNode创建一个SkSurface,并且保存到layers中去
  2. 渲染的时候,会先去渲染这些layer,因此传入mComposeLayer为false,因此会执行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创建好后,如果没有发生变化,则不会设置成layer的RenderNode创建新的layer,也不会出现再layers里面,但它持有的原来的layer,因此再绘制帧的时候直接进入第3步,从而得到优化。
  5. Layer除了能内容没有发生变化的时候,可以重用之前的绘制的SkImage外,也可以作为一个整体应用某些属性。

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
  IinT9K6LsFrg   2023年12月23日   60   0   0 锚点CSS锚点html5html5CSS
mkIZDEUN7jdf