美文网首页
硬件渲染-绘制一帧

硬件渲染-绘制一帧

作者: gczxbb | 来源:发表于2018-10-01 19:36 被阅读47次

前面已经介绍过同步帧状态。 硬件渲染一次任务整体流程图.jpg

继续分析绘制一帧的相关内容。


绘制一帧

从CanvasContext#draw方法开始。

void CanvasContext::draw() {
    SkRect dirty;
    EGLint width, height;
    mEglManager.beginFrame(mEglSurface, &width, &height);
    //如果宽高与OpenGLRenderer内部CanvasState保存的不同。
    //重新设置到CanvasState中。
    if (width != mCanvas->getViewportWidth() || height != mCanvas->getViewportHeight()) {
        //初始化CanvasState的宽高。
        mCanvas->setViewport(width, height);
        dirty.setEmpty();
    } else if (!mBufferPreserved || mHaveNewSurface) {
        dirty.setEmpty();//新Surface,脏区域置空
    } else {
        if (!dirty.isEmpty() && !dirty.intersect(0, 0, width, height)) {
            //区域与该View无交集,置空
            dirty.setEmpty();
        }
        profiler().unionDirty(&dirty);
    }
    //不空,则开始刷脏区域,第一步
    if (!dirty.isEmpty()) {
        mCanvas->prepareDirty(dirty.fLeft, dirty.fTop,
                dirty.fRight, dirty.fBottom, mOpaque);
    } else {
        //刷新整个视图。
        mCanvas->prepare(mOpaque);
    }
    Rect outBounds;
    //第二步,OpenGLRenderer绘制
    mCanvas->drawRenderNode(mRootRenderNode.get(), outBounds);
    profiler().draw(mCanvas);
    bool drew = mCanvas->finish();
    mCurrentFrameInfo->markSwapBuffers();
    if (drew) {
        //第三步
        swapBuffers(dirty, width, height);
    }
    mCurrentFrameInfo->markFrameCompleted();
    mJankTracker.addFrame(*mCurrentFrameInfo);
    mRenderThread.jankTracker().addFrame(*mCurrentFrameInfo);
}

CanvasContext#initialize方法,初始化OpenGLRenderer。即内部mCanvas,draw方法中很多依赖OpenGLRenderer完成。
绘制一帧主要三个部分。
第一部分:OpenGLRenderer#prepareDirty方法。
准备刷新视图脏区域,若脏区域为空,通过prepare方法调用prepareDirty刷新视图整个宽高区域,入参包括不透明标志(不透明为true)。
第二部分:OpenGLRenderer#drawRenderNode方法。
根节点RootRenderNode绘制。入参是根节点。

第一部分 prepareDirty分析

prepareDirty方法流程图如下。 OpenGLRenderer的prepareDirty流程解析.jpg
void OpenGLRenderer::prepareDirty(float left, float top,
        float right, float bottom, bool opaque) {
    setupFrameState(left, top, right, bottom, opaque);
    //从CanvasState的Snapshot链表获取Snapshot对象。
    if (currentSnapshot()->fbo == 0) {
        mRenderState.blend().syncEnabled();
        updateLayers();
    } else {
        //fbo存在,初始化opengl状态
        startFrame();
    }
}

由图可知,分两步
1:setupFrameState,初始化SaveStack。
2:从CanvasState获取当前Snapshot,判断fbo。

void OpenGLRenderer::setupFrameState(float left, float top,
        float right, float bottom, bool opaque) {
    //清理Cache,包括TextureCache、PathCache
    //和PatchCache。
    mCaches.clearGarbage();
    mState.initializeSaveStack(left, top, right, bottom, mLightCenter);
    mOpaque = opaque;//CanvasContext中的mOpaque 
    mTilingClip.set(left, top, right, bottom);
}

OpenGLRenderer内部CanvasState。

void CanvasState::initializeSaveStack(float clipLeft, float clipTop,
        float clipRight, float clipBottom, const Vector3& lightCenter) {
    //mSnapshot指向链表表头
    mSnapshot = new Snapshot(mFirstSnapshot,
            SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
    mSnapshot->setClip(clipLeft, clipTop, clipRight, clipBottom);
    //mCanvas是OpenGLRenderer
    mSnapshot->fbo = mCanvas.getTargetFbo();
    mSnapshot->setRelativeLightCenter(lightCenter);
    mSaveCount = 1;//Save栈的初始数量设为1。
}

CanvasState构造方法创建第一个Snapshot,此方法再创建一个,链表表头。初始化Snapshot的fbo,帧缓存对象。

OpenGLRenderer结构关系图。

OpenGLRenderer相关结构关系图.jpg OpenGLRenderer与LayerRenderer。
1:LayerRenderer继承OpenGLRenderer,OpenGLRenderer继承CanvasStateClient。他们内部均包含CanvasState,指向创建它的CanvasStateClient。
2:OpenGLRenderer/LayerRenderer构造方法创建CanvasState。
3:当前CanvasState#Snapshot的fbo由与CanvasState关联的render设置,render是LayerRenderer或OpenGLRenderer。
4:他们均重写基类CanvasStateClient#getTargetFbo方法。返回值不同。
OpenGLRenderer:返回0,因此Snapshot的fbo被设置为0。
LayerRenderer:返回内部Layer的fbo。

CanvaState的render是OpenGLRenderer,不是LayerRenderer,因此,Snapshot初始化fbo是0
官方注释
framebuffer渲染器(fbo是0)将(使用fbo帧缓冲对象)会推迟每一个Layer的display list,等待直到第一个渲染命令,开始绘制帧。
Blend#syncEnabled同步方法根据mEnabled状态去调用glEnable(GL_BLEND)后者glDisable(GL_BLEND),然后触发OpenGLRenderer#updateLayers方法更新Layers。
Layer渲染器(fbo非0)立即开始渲染,startFrame方法。

OpenGLRenderer#updateLayers分析

void OpenGLRenderer::updateLayers() {
    int count = mLayerUpdates.size();
    if (count > 0) {
        for (int i = 0; i < count; i++) {
            Layer* layer = mLayerUpdates.itemAt(i).get();
            updateLayer(layer, false);
        }

        if (CC_UNLIKELY(Properties::drawDeferDisabled)) {
            mLayerUpdates.clear();
            mRenderState.bindFramebuffer(getTargetFbo());
        }
    }
}

刷新Layer数组中每一个Layer。LayerRenderer未重写此方法。

在OpenGLRenderer中的Layer,就是OpenGLRenderer渲染器最终将要渲染的Layer了。
那么,问题,数组元素Layer是哪里来的?

OpenGLRenderer#pushLayerUpdate方法负责向数组push元素(保证不相等的Layer对象)。
两个地方触发该方法。
1:CanvasContext#processLayerUpdate方法。
2:RenderNode#pushLayerUpdate(TreeInfo& info)方法。
第一个方法,前面也提到过,针对TextureView视图。入参是DeferredLayerUpdater,对应上层HardwareLayer。
这里不详细探讨TextureView视图中HardwareLayer的创建过程。前面也说过,Layer无deferredUpdateScheduled标志,不会被加入OpenGLRenderer的Layer数组。
第二个方法,前面提到过,发生在每一个RenderNode节点prepareTreeImpl方法中,设置LAYER_TYPE_HARDWARE的视图,LayerRenderer负责创建RenderNode节点的内部Layer,Layer也包含RenderNode。
LayerRenderer结构关系图。

LayerRenderer结构关系图.jpg

因此,内部Layer数组与RenderNode的Layer有关。
刷新每一个Layer。

bool OpenGLRenderer::updateLayer(Layer* layer, bool inFrame) {
    //RenderNode内部Layer
    if (layer->deferredUpdateScheduled && layer->renderer
            && layer->renderNode.get() && layer->renderNode->isRenderable()) {
        if (inFrame) {
            endTiling();
            debugOverdraw(false, false);
        }
        //Layer渲染时,对drawDeferDisabled标志的判断与drawRenderNode方法一样。
        //此外,这里还要判断inFrame标志。
        if (CC_UNLIKELY(inFrame || Properties::drawDeferDisabled)) {
            layer->render(*this);
        } else {
            layer->defer(*this);
        }
        if (inFrame) {
            resumeAfterLayer();
            startTilingCurrentClip();
        }
        layer->debugDrawUpdate = Properties::debugLayersUpdates;
        layer->hasDrawnSinceUpdate = false;
        return true;
    }
    return false;
}

刷新某一Layer应满足以下条件。

  • 1:deferredUpdateScheduled标志。

注意一下:
前面文章也介绍过,在源码中,只有Layer#updateDeferred方法设置此标志。在数据同步时,RenderNode节点初始化内部Layer(上层设置条件下),此方法除了设置该标志,还会创建LayerRenderer。
这一切的前提是上层视图设置LAYER_TYPE_HARDWARE标志。
TextureView视图的底层Layer
并不会调用这个方法。**

  • 2:Layer内部,RenderNode存在且DisplayListData数据不空。

节点内部Layer由LayerRenderer#createRenderLayer创建。只有这种Layer才有RenderNode,将LayerRenderer设置内部渲染器。

  • 3:Layer渲染器renderer存在,是LayerRenderer。

OpenGLRenderer#updateLayer方法刷新的Layer与视图TextureView的底层Layer无关。

目前介绍两种Layer,还有一种Layer,由OpenGLRenderer#的createLayer方法创建。OpenGLRenderer#saveLayer触发。上层Canvas#saveLayer时,触发一次SaveLayerOp操作,此操作会导致渲染器#saveLayer。

三种Layer如下图所示。 三种Layer.jpg

综上:
第一种Layer,上层saveLayer方法通过createLayer方法创建Layer。Layer对象也会通过fboLayer标志绑定fbo。
第二种Layer,TextureView视图,LayerRenderer#createTextureLayer创建。
第三种Layer,上层视图LAYER_TYPE_HARDWARE,底层RenderNode节点pushLayerUpdate时创建Layer,Layer内部有LayerRenderer渲染器和RenderNode节点。
LayerRenderer#createRenderLayer方法创建。此Layer一定会绑定一个fbo。
fbo在FboCache中通过glGenFramebufferss生成帧缓冲区对象。

Layer渲染

OpenGLRenderer#updateLayer(Layer* layer, bool inFrame)方法,触发Layer渲染。
支持两种渲染defer与render,defer还是render?
drawDeferDisabled禁止延迟或入参inFrame是true,render立即渲染,否则defer延迟合并。这两种情况分两个讨论,因为,每个操作命令均支持render和defer。
在OpenGLRenderer#drawLayer处,触发updateLayer时,inFrame是true。
下面先分别简单介绍。
Layer立即渲染

void Layer::render(const OpenGLRenderer& rootRenderer) {
    //设置Layer的宽高,保存在CanvasState中
    renderer->setViewport(layer.getWidth(), layer.getHeight());
    //专门为Layer渲染的对象LayerRenderer。
    //与普通渲染一样的方法逻辑。
    renderer->prepareDirty(dirtyRect.left, dirtyRect.top,
                dirtyRect.right, dirtyRect.bottom,!isBlend());
    renderer->drawRenderNode(renderNode.get(), dirtyRect, 
                RenderNode::kReplayFlag_ClipChildren);
    renderer->finish();
    dirtyRect.setEmpty();
    deferredUpdateScheduled = false;
    renderNode = nullptr;
}

注意:Layer内部render是LayerRenderer。Layer中不一定会有RenderNode。
1:LayerRenderer#prepareDirty方法准备脏区域,这时触发prepareDirty的是LayerRenderer,因此,currentSnapshot的fbo>0,不会再进入updateLayers。
2:OpenGLRenderer#drawRenderNode,前面也会调用这个方法,绘制的是CanvasContext的根RenderNode节点,这里绘制Layer内部RenderNode节点。马上要分析的第二部分。
3:最后,将恢复deferredUpdateScheduled延迟标志,渲染节点renderNode置null,代表Layer渲染结束。
Layer延迟合并

void Layer::defer(const OpenGLRenderer& rootRenderer) {
    const float width = layer.getWidth();
    const float height = layer.getHeight();
    if (dirtyRect.isEmpty() || (dirtyRect.left <= 0 && dirtyRect.top <= 0 &&
            dirtyRect.right >= width && dirtyRect.bottom >= height)) {
        dirtyRect.set(0, 0, width, height);//设置Layer的脏区域
    }
    //创建DeferredDisplayList
    //封装在结构体DeferStateStruct。
    deferredList.reset(new DeferredDisplayList(dirtyRect));
    DeferStateStruct deferredState(*deferredList, *renderer,
            RenderNode::kReplayFlag_ClipChildren);
    //设置Layer的宽高,保存在CanvasState中。
    renderer->setViewport(width, height);
    renderer->setupFrameState(dirtyRect.left, dirtyRect.top,
            dirtyRect.right, dirtyRect.bottom, !isBlend());
    renderNode->computeOrdering();
    renderNode->defer(deferredState, 0);
    deferredUpdateScheduled = false;
}

创建一个DeferredDisplayList数据,封装在结构体DeferStateStruct,Layer内部RenderNode#refer方法。

综上:Layer渲染的本质是,利用内部LayerRenderer,渲染内部RenderNode的过程。
RenderNode渲染本质
延迟合并:本质是RenderNode#defer方法。
立即渲染:本质是LayerRenderer的drawRenderNode方法,即OpenGLRenderer的drawRenderNode方法。在该方法中,再次判断RenderNode#replay还是defer。

总结

prepareDirty方法
初始化帧状态:初始化画布状态,创建Snapshot,设置Snapshot的区域,对于帧缓存对象fbo,只有LayerRenderer才会有,普通视图暂不考虑,初始化fbo是0。初始化当前Save层值是1。
OpenGLRenderer中Layer刷新:普通视图暂不考虑。针对上层设置LAYER_TYPE_HARDWARE的RenderNode节点。

下面,就看第二部分OpenGLRenderer#drawRenderNode方法吧。


第二部分,drawRenderNode分析

每个节点代表一个视图数据,绘制视图某一个dirty区域内容。该方法真正开始绘制节点。以CanvasContext#draw方法绘制RootRenderNode根节点为主进行介绍。

//this看情况,也可能是LayerRenderer
void OpenGLRenderer::drawRenderNode(RenderNode* renderNode, Rect& dirty, int32_t replayFlags) {
    //renderNode和mDisplayListData不空
    if (renderNode && renderNode->isRenderable()) {
        renderNode->computeOrdering();
        //为true时,则禁止defer,禁止合并
        if (CC_UNLIKELY(Properties::drawDeferDisabled)) {
            startFrame();
            ReplayStateStruct replayStruct(*this, dirty, replayFlags);
            renderNode->replay(replayStruct, 0);//立即绘制
            return;
        }
        //合并延迟
        bool avoidOverdraw = !Properties::debugOverdraw;
        DeferredDisplayList deferredList(mState.currentClipRect(), avoidOverdraw);
        DeferStateStruct deferStruct(deferredList, *this, replayFlags);
        renderNode->defer(deferStruct, 0);
        flushLayers();
        startFrame();
        deferredList.flush(*this, dirty);
    } else {
        startFrame();
    }
}

前提条件待绘制节点和DisplayListData信息均不空。
(有时Layer中的RenderNode是空,在Layer渲染时注意。)
RenderNode立即渲染与延迟合并流程图如下。

RenderNode立即绘制与延迟合并流程图.jpg

二者区别。
立即渲染:在遍历整个操作过程中,遇到一个DrawOp绘制操作或StateOp状态操作,立即写入Opengl。
延迟合并渲染:将操作收集DeferredDisplayList,当遇到flush时,集中批次写入Opengl。

#reply方法
void RenderNode::replay(ReplayStateStruct& replayStruct, const int level) {
    ReplayOperationHandler handler(replayStruct, level);
    issueOperations<ReplayOperationHandler>(replayStruct.mRenderer, handler);
}
#defer方法
void RenderNode::defer(DeferStateStruct& deferStruct, const int level) {
    DeferOperationHandler handler(deferStruct, level);
    issueOperations<DeferOperationHandler>(deferStruct.mRenderer, handler);
}

两者均初始化handler,ReplayOperationHandler与DeferOperationHandler,触发issueOperations方法,defer与reply模式。利用两个handler分别触发Op的reply与defer。
issueOperations方法将多次触发handler(XxxOp,saveCount,clipToBounds)方法。
两个handler内联函数如下。

//ReplayOperationHandler内联函数
inline void operator()(DisplayListOp* operation, int saveCount, bool clipToBounds) {
    operation->replay(mReplayStruct, saveCount, mLevel, clipToBounds);
}
//DeferOperationHandler内联函数。
inline void operator()(DisplayListOp* operation, int saveCount, bool clipToBounds) {
    operation->defer(mDeferStruct, saveCount, mLevel, clipToBounds);
}

综上
ReplayOperationHandler(XxxOp,..)的本质是XxxOp#replay方法。
DeferOperationHandler(XxxOp,..)的本质是XxxOp#defer方法。

RenderNode#issueOperations方法。

template <class T>
void RenderNode::issueOperations(OpenGLRenderer& renderer, T& handler) {
    //内部mLayer存在且
    //mLayer的renderer与传入的OpenGLRenderer不同
    const bool drawLayer = (mLayer && (&renderer != mLayer->renderer.get()));
    ...
    LinearAllocator& alloc = handler.allocator();
    int restoreTo = renderer.getSaveCount();
    handler(new (alloc) SaveOp(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag),
            PROPERTY_SAVECOUNT, properties().getClipToBounds());
    bool quickRejected = properties().getClipToBounds()
            && renderer.quickRejectConservative(0, 0, properties().getWidth(), properties().getHeight());
    if (!quickRejected) {
        Matrix4 initialTransform(*(renderer.currentTransform()));
        renderer.setBaseTransform(initialTransform);
        //第三种Layer的情形,只考虑Layer绘制
        if (drawLayer) {
            handler(new (alloc) DrawLayerOp(mLayer, 0, 0),
                    renderer.getSaveCount() - 1, properties().getClipToBounds());
        } else {//针对给View节点的操作数据
            暂时省略...后面介绍
        }
    }
    handler(new (alloc) RestoreToCountOp(restoreTo),
            PROPERTY_SAVECOUNT, properties().getClipToBounds());
}

ReplayOperationHandler或DeferOperationHandler ,触发的XxxOp方法不同。

renderer是什么呢?
若是在CanvasContext#draw中绘制根节点。renderer即OpenGLRenderer。
若是在Layer#render中绘制RenderNode,renderer即LayerRenderer。

开始时有一个判断。
若RenderNode内Layer存在且LayerRenderer。前面已经介绍过,这属于第三种Layer的情况,仅考虑DrawLayerOp绘制。Layer内部render是LayerRenderer,一定存在fbo。

DrawLayerOp继承DrawOp。DrawLayerOp#defer或replay。

DrawLayerOp未重写replay,重写applyDraw。在DrawOp#replay触发子类重写的applyDraw。
DrawLayerOp#applyDraw方法。

//封装在ReplayStateStruct的OpenGLRenderer。
virtual void applyDraw(OpenGLRenderer& renderer, Rect& dirty) override {
    renderer.drawLayer(mLayer, mX, mY);
}

DrawLayerOp未重写defer,父类DrawOp的defer方法。

virtual void defer(DeferStateStruct& deferStruct, int saveCount, int level,
            bool useQuickReject) override {
    if (mQuickRejected && CC_LIKELY(useQuickReject)) {
        return;
    }
   deferStruct.mDeferredList.addDrawOp(deferStruct.mRenderer, this);
}

将DrawLayerOp操作加入到DeferStateStruct结构体的DeferredList列表。其他DrawOp子类defer也是同样的逻辑。

总结

OpenGLRenderer#drawRenderNode的本质是
将绘制任务交给入参RenderNode节点的defer与replay方法完成。前提是RenderNode存在且DisplayListData数据不空。
1:RenderNode的defer与replay将触发XxxOp的对应方法。
2:不仅DrawLayerOp,每一种DrawOp子类均重写applyDraw,触发OpenGLRenderer#drawXxx。
3:Op#defer向DeferredDisplayList加入操作,Op#reply申请applyDraw绘制。
4:defer后,相关Op已保存在DeferredDisplayList中,由DeferredDisplayList#flush刷新。
reply操作,最终均走到OpenGLRenderer#drawXXX方法,然后通过OpenGLRenderer::renderGlop(const Glop& glop, GlopRenderType type),写入opengl。
defer操作
DeferStateStruct结构体的DeferredDisplayList列表存储了绘制操作。

到这里,绘制一帧基本流程介绍完成。

接下来,看一下Save与RestoreToCount操作,以及延迟合并的逻辑。


SaveOp与RestoreToCountOp操作

RenderNode#issueOperations方法开始于结束会触发一次
一次SaveOp和RestoreToCountOp操作,他们均继承StateOp。
issueOperations代码段#

{
//先获取CanvasState中的mSaveCount
int restoreTo = renderer.getSaveCount();

//根据操作符号,调用的是SaveOp的defer方法
handler(new (alloc) SaveOp(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag),
            PROPERTY_SAVECOUNT, properties().getClipToBounds());
...
//根据操作符号,调用RestoreToCountOp#defer方法
handler(new (alloc) RestoreToCountOp(restoreTo),
            PROPERTY_SAVECOUNT, properties().getClipToBounds());
}

下面分析这两个操作。先看SaveOp。
handler触发SaveOp#defer方法,结构体DeferStateStruct的DeferredDisplayList。

virtual void defer(DeferStateStruct& deferStruct, int saveCount, int level,
            bool useQuickReject) override {
    int newSaveCount = deferStruct.mRenderer.save(mFlags);
    deferStruct.mDeferredList.addSave(deferStruct.mRenderer, this, newSaveCount);
}

DeferStateStruct的mRenderer是OpenGLRenderer,OpenGLRenderer#save,触发CanvasState#save,最后调用CanvasState#saveSnapshot。

int CanvasState::saveSnapshot(int flags) {
    mSnapshot = new Snapshot(mSnapshot, flags);
    //先返回mSaveCount,再自增。
    return mSaveCount++;
}

创建一个Snapshot,插入表头。CanvasState内部mSaveCount返回,并自增。

//newSaveCount是saveSnapshot返回的值
void DeferredDisplayList::addSave(OpenGLRenderer& renderer, SaveOp* op, int newSaveCount) {
    int saveFlags = op->getFlags();
    if (recordingComplexClip() && (saveFlags & SkCanvas::kClip_SaveFlag)) {
        storeStateOpBarrier(renderer, op);
        //进入的newSaveCount是自增前的值。也就是需要恢复的值。
        mSaveStack.push(newSaveCount);
    }
}

更新DeferredDisplayList数据,SaveStack和Batches。
将返回的mSaveCount值push到SaveStack数组。

void DeferredDisplayList::storeStateOpBarrier(OpenGLRenderer& renderer, StateOp* op) {
    DeferredDisplayState* state = createState();
    renderer.storeDisplayState(*state, getStateOpDeferFlags());
    mBatches.add(new StateOpBatch(op, state));
    resetBatchingState();
}

创建DeferredDisplayState,并设置DeferredDisplayState的一些值。
Batches新增一个
StateOpBatch,**它封装了DeferredDisplayState与Op。

RestoreToCountOp同SaveOp类似。RestoreToCountOp#defer方法。

virtual void defer(DeferStateStruct& deferStruct, int saveCount, int level,
            bool useQuickReject) override {
    deferStruct.mDeferredList.addRestoreToCount(deferStruct.mRenderer,
                this, saveCount + mCount);
    deferStruct.mRenderer.restoreToCount(saveCount + mCount);
}

mCount是RestoreToCountOp构建传入的参数,即CanvasState#getSaveCount当时返回的值,意思是恢复到此时的数量,saveCount值是0,PROPERTY_SAVECOUNT。
DeferredDisplayList#addRestoreToCount方法。

void DeferredDisplayList::addRestoreToCount(OpenGLRenderer& renderer, StateOp* op,
        int newSaveCount) {   
    if (recordingComplexClip() && newSaveCount <= mComplexClipStackStart) {
        mComplexClipStackStart = -1;
        resetBatchingState();
    }
    //newSaveCount是恢复的数值
    if (mSaveStack.isEmpty() || newSaveCount > mSaveStack.top()) {
        return;
    }
    //当SaveOp时,将要恢复的值会push到mSaveStack栈顶。
    while (!mSaveStack.isEmpty() && mSaveStack.top() >= newSaveCount) mSaveStack.pop();
    storeRestoreToCountBarrier(renderer, op, mSaveStack.size() + FLUSH_SAVE_STACK_DEPTH);
}

第一步:若saveOp与RestoreToCountOp成对出现,则mSaveStack最上面保存的值就是自增前的mSaveCount,与要恢复的这个数值newSaveCount相等的,直接pop即可。
若newSaveCount大于mSaveStack最上面元素,说明已经有过RestoreToCountOp操作,这种情况直接退出。
若newSaveCount小于mSaveStack最上面元素,说明可能有多个SaveOp操作,则mSaveStack一直pop,直到达到我们需要恢复的层级,即栈顶等于newSaveCount。
第二步:OpenGLRenderer#restoreToCount,即在CanvasState中恢复到saveCount。

void CanvasState::restoreToCount(int saveCount) {
    if (saveCount < 1) saveCount = 1;
    while (mSaveCount > saveCount) {
        restoreSnapshot();
    }
}

逻辑基本与上面一致,若两种Op成对出现,CanvasState内部的mSaveCount值会比saveCount大1,即mSaveCount需要恢复自增前的值,若出现过多个SaveOp,mSaveCount将远大于saveCount,不停的自减且删除Snapshot节点,直到相等为止。

总之,每一次SaveOp,在CanvasState中创建一个Snapshot,并更新DeferredDisplayList的SaveStack和Batches数据。SaveStack栈顶存储CanvasState自增前值。
每一次RestoreToCountOp,删除CanvasState的Snapshot,恢复内部mSaveCount,SaveStack栈顶弹出即将恢复的值。


普通视图RenderNode节点#issueOperations方法

前面介绍过Layer时仅绘制一个DrawLayerOp,下面介绍无Layer时,一个普通视图的绘制逻辑。
RenderNode#issueOperations代码段。

//针对给View节点的操作数据,//上面暂时省略的内容 
{
    const int saveCountOffset = renderer.getSaveCount() - 1;
    const int projectionReceiveIndex = mDisplayListData->projectionReceiveIndex;
    for (size_t chunkIndex = 0; chunkIndex < mDisplayListData->getChunks().size(); chunkIndex++) {
        const DisplayListData::Chunk& chunk = mDisplayListData->getChunks()[chunkIndex];
        Vector<ZDrawRenderNodeOpPair> zTranslatedNodes;
        //子RenderNode排序Z轴。
        buildZSortedChildList(chunk, zTranslatedNodes);
        issueOperationsOf3dChildren(kNegativeZChildren,
                        initialTransform, zTranslatedNodes, renderer, handler);//Z轴负数
        for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
                    DisplayListOp *op = mDisplayListData->displayListOps[opIndex];
            handler(op, saveCountOffset, properties().getClipToBounds());
            if (CC_UNLIKELY(!mProjectedNodes.isEmpty() && projectionReceiveIndex >= 0 &&
                opIndex == static_cast<size_t>(projectionReceiveIndex))) {
                issueOperationsOfProjectedChildren(renderer, handler);
            }
        }
        issueOperationsOf3dChildren(kPositiveZChildren,
                        initialTransform, zTranslatedNodes, renderer, handler);//Z轴正数
    }
}

DisplayListData内部数组Chunk。触发以下三个方法

RenderNode#buildZSortedChildList。
RenderNode#issueOperationsOf3dChildren。
RenderNode#issueOperationsOfProjectedChildren。

排序Z轴子视图

void RenderNode::buildZSortedChildList(const DisplayListData::Chunk& chunk,
        Vector<ZDrawRenderNodeOpPair>& zTranslatedNodes) {
    if (chunk.beginChildIndex == chunk.endChildIndex) return;

    for (unsigned int i = chunk.beginChildIndex; i < chunk.endChildIndex; i++) {
        DrawRenderNodeOp* childOp = mDisplayListData->children()[i];
        //获取DrawRenderNodeOp对应RenderNode
        RenderNode* child = childOp->mRenderNode;
        float childZ = child->properties().getZ();
        //加入zTranslatedNodes数组。
        if (!MathUtils::isZero(childZ) && chunk.reorderChildren) {
            zTranslatedNodes.add(ZDrawRenderNodeOpPair(childZ, childOp));
            //设置mSkipInOrderDraw标志。
            childOp->mSkipInOrderDraw = true;
        } else if (!child->properties().getProjectBackwards()) {
            // regular, in order drawing DisplayList
            childOp->mSkipInOrderDraw = false;
        }
    }
    //从小到大的顺序
    std::stable_sort(zTranslatedNodes.begin(), zTranslatedNodes.end());
}

DisplayListData中DrawRenderNodeOp数组mChildren。
遍历,遍历的索引范围是Chunk中beginChildIndex和endChildIndex。
从每项DrawRenderNodeOp获取RenderNode。目的是拿到RenderNode的Z轴值。

上层DisplayListCanvas绘制Renderode时(drawRenderNode方法),创建一个DrawRenderNodeOp,并将绘制的RenderNode与它绑定。

RenderNode属性RenderProperties的Z值,这是Z轴方向的距离。即etElevation() + getTranslationZ()。
如果childZ不是0并且Chunk的reorderChildren重排标志位true,即该Chunk创建前insertReorderBarrier插入的是需要重排的类型kBarrier_OutOfOrder。

创建ZDrawRenderNodeOpPair加入zTranslatedNodes数组中,封装DrawRenderNodeOp与childZ。
最后,zTranslatedNodes数组按childZ由小到大stable_sort排序。

RenderNode#issueOperationsOf3dChildren方法。
子节点DrawRenderNodeOp操作经过排序后,childZ从小到大。

先调用一次issueOperationsOf3dChildren方法

先触发childZ是负数的,这种情况比较简单是没有阴影的,直接处理所有Z轴为负数的DrawRenderNodeOp。

处理本视图绘制操作

遍历displayListOps数组
当前Chunk的beginOpIndex和endOpIndex索引指向的数组displayListOps中的一段操作执行绘制。

最后在调用一次issueOperationsOf3dChildren方法

执行childZ是非负数,从最小的非负数childZ开始,先绘制阴影issueDrawShadowOperation操作,如果下一个索引的childZ和前一个childZ差距小与一个值,则继续绘制该索引DrawRenderNodeOp对应的阴影。不满足条件则绘制DrawRenderNodeOp,当索引达到shadowIndex时候,则绘制阴影DrawShadowOp操作。每一个索引的阴影和DrawRenderNodeOp都会绘制,顺序不是按照index一次执行的。

这次Chunk就结束了,接下来是下一个Chunk。

当Vector<DrawRenderNodeOp*> mProjectedNodes投影数组不为空时,issueOperationsOfProjectedChildren操作投影DrawRenderNodeOp。遍历mProjectedNodes。

DrawRenderNodeOp操作的本质是调用内部RenderNode的defer或reply方法


延迟合并渲染

先从前面拿到延迟合并渲染的一段代码。
OpenGLRenderer#drawRenderNode方法代码段。

{
    ...
    bool avoidOverdraw = !Properties::debugOverdraw;
    DeferredDisplayList deferredList(mState.currentClipRect(), avoidOverdraw);
    DeferStateStruct deferStruct(deferredList, *this, replayFlags);
    //节点延迟合并渲染
    renderNode->defer(deferStruct, 0);
    flushLayers();
    startFrame();
    //刷新
    deferredList.flush(*this, dirty);
}

从RenderNode#defer方法,进入issueOperations,入参handler是DeferOperationHandler。因此,执行每一个handler触发Op的defer方法。
绘制操作基类DrawOp,子类包括DrawColorOp、DrawBoundedOp、以及前面提到过的DrawLayerOp等。他们均重用DrawOp#defer方法。此方法前面已经提到过,这里重新拿来分析。

virtual void defer(DeferStateStruct& deferStruct, int saveCount, int level,
            bool useQuickReject) override {
    if (mQuickRejected && CC_LIKELY(useQuickReject)) {
        return;
    }
   deferStruct.mDeferredList.addDrawOp(deferStruct.mRenderer, this);
}

分析DeferredDisplayList的addDrawOp方法。
绘制操作DrawOp加入DeferredDisplayList。

void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) {
    DeferredDisplayState* const state = createState();    
    ...
    //初始化DeferInfo信息。
    DeferInfo deferInfo;
    op->onDefer(renderer, deferInfo, *state);

    deferInfo.mergeable &= !recordingComplexClip();
    deferInfo.opaqueOverBounds &= !recordingComplexClip()
            && mSaveStack.isEmpty()
            && !state->mRoundRectClipState;
 
    DrawBatch* targetBatch = nullptr; 
    //默认插入到Batch数组的尾部
    int insertBatchIndex = mBatches.size();
    //Batches数组集合是不空的条件下
    //考虑mMergingBatches和mBatchLookup。
    //MergingBatches是一个HashMap数组。
    if (!mBatches.isEmpty()) {
        if (state->mBounds.isEmpty()) {
            // don't know the bounds for op, so add to last batch and start from scratch on next op
            DrawBatch* b = new DrawBatch(deferInfo);
            b->add(op, state, deferInfo.opaqueOverBounds);
            mBatches.add(b);
            resetBatchingState();
            return;
        }
        //可以合并。
        if (deferInfo.mergeable) {
            //根据batchId和mergeId
            //从MergingBatches查找batch。
            if (mMergingBatches[deferInfo.batchId].get(deferInfo.mergeId, targetBatch)) {
                if (!((MergingDrawBatch*) targetBatch)->canMergeWith(op, state)) {
                    targetBatch = nullptr;
                }
            }
        //不可合并
        } else {
            //根据batchId从BatchLookup查。
            targetBatch = (DrawBatch*)mBatchLookup[deferInfo.batchId];
        }
        //找到了目标或可合并。
        if (targetBatch || deferInfo.mergeable) {
            for (int i = mBatches.size() - 1; i >= mEarliestBatchIndex; i--) {
                DrawBatch* overBatch = (DrawBatch*)mBatches[i];
                //遍历每个DrawBatch,三种情况下break
                if (overBatch == targetBatch) break;
                if (deferInfo.batchId == overBatch->getBatchId()) {
                    insertBatchIndex = i + 1;
                    //可以合并的,为后面的新建找到插入位置。
                    //退出。
                    if (!targetBatch) break;
                }
                if (overBatch->intersects(state->mBounds)) {
                    targetBatch = nullptr;
                    break;
                }
            }
        }
    }
    //目标批次不存在,创建,插入数组相应位置。
    //MergingDrawBatch或DrawBatch批次。
    if (!targetBatch) {
        if (deferInfo.mergeable) {
            targetBatch = new MergingDrawBatch(deferInfo,
                    renderer.getViewportWidth(), renderer.getViewportHeight());
            mMergingBatches[deferInfo.batchId].put(deferInfo.mergeId, targetBatch);
        } else {
            targetBatch = new DrawBatch(deferInfo);
            mBatchLookup[deferInfo.batchId] = targetBatch;
        }
        mBatches.insertAt(targetBatch, insertBatchIndex);
    }
    //最终,找到目标Batch,存放op。
    targetBatch->add(op, state, deferInfo.opaqueOverBounds);
}

每一种DrawOp的子类均对应一个BatchId。枚举变量OpBatchId存储BatchId类型。

OpBatchId枚举变量
kOpBatch_None = 0, // Don't batch
kOpBatch_Bitmap,
kOpBatch_Patch,
kOpBatch_AlphaVertices,
kOpBatch_Vertices,
kOpBatch_AlphaMaskTexture,
kOpBatch_Text,
kOpBatch_ColorText,

kOpBatch_Count, // Add other batch ids before this

mBatchLookup和mMergingBatches是一个数组Map,,枚举值数量kOpBatch_Count。
mBatchLookup:根据BatchId枚举值获取Batch。

BatchLookup数组枚举.jpg

mMergingBatches:根据BatchId枚举值获取一个Map,Key是merageId。

MergingBatches数组枚举.jpg

子类重写DrawOp#onDefer方法,初始化DeferInfo结构体特定batchId,其他信息包括mergeable,mergeId等,每一个子类有所不同。
然后根据条件选择或者创建一个批次,该DrawOp加入批次。
DeferredDisplayList中有一个Batch数组。最终我们会找到目标Batch,即targetBatch。Batch#add方法将此DrawOp加入。
DeferInfo#mergeable决定从MergingBatches还是从BatchLookup获取Batch。

若无mergeable标志。在BacthLookup中查找

1:若未找到batchId对应的目标批次,创建。
2:若找到batchId对应的目标批次,从后向前遍历Batch数组。
以下对应三种情况的break退出。

  • targetBatch已经在Batch数组中了,退出循环,这就是目标批次。
  • 还未找到一样的批次,却在数组中找到了batchId相等的批次,设置插入位置这个批次的后面。这里考虑到数组中找不到一样的批次,未来新建时的插入位置(仅在后面创建插入时用)。这种情况下targetBatch一直存在,继续循环。
  • 若遍历的批次,操作区域与需加入的op区域有重合,则为op单独准备批次。这种情况要成立,就一定要新建批次了。退出循环。

若有mergeable标志。在MergingBatches中查找

1:根据batchId查找HashMap,再根据mergeId查找,经canMergeWith方法判断合适,即找到targetBatch。
2:根据代码可知,不管是否找到批次,均会遍历数组。

找到批次的三种break同上。
以下情况是未找到批次时。

  • 永远在Batch数组中无法找到。
  • 数组中找到了batchId相等的批次,同样,设置插入位置这个批次的后面。这种情况下目标批次是空,一定会新建批次,因此,设置其插入位置还是需要的。找到位置后,就没有必要继续了,退出循环。
  • 若遍历的批次,操作区域与需加入的op区域有重合,则为op单独准备批次。本来就未找到,这种情况必定新建批次了。

最终,
目标批次不空:将Drawop合并到该批次事务中。
目标批次是空:创建之,并将新Batch设置成其BatchId枚举值在MergingBatches或BatchLookup中对应的Value,插入Batches数组。因此,只要在Batches数组出现的,一定在MergingBatches或BatchLookup中根据BatchId能找到原件。


Batch是什么

DrawBatch#add方法。

virtual void add(DrawOp* op, const DeferredDisplayState* state, bool opaqueOverBounds) {
    mBounds.unionWith(state->mBounds);
    mAllOpsOpaque &= opaqueOverBounds;
    mOps.add(OpStatePair(op, state));
}

DrawOp和DeferredDisplayState封装成一个OpStatePair,加入OpStatePair数组。
每一个Batch包含一个OpStatePair数组,一个区域Rect。新建DrawBatch的mMergeId和mBatchId由DeferInfo设置。
一个DrawOp操作对应一个OpStatePair。

Batch结构图.jpg

MergingDrawBatch继承DrawBatch,DrawBatch继承Batch,Batch是批处理,将一系列DrawOp集合成一个批次。集中写入GPU。


批处理刷新

绘制操作已经保存在Batch批次中。

void DeferredDisplayList::flush(OpenGLRenderer& renderer, Rect& dirty) {
    Caches::getInstance().fontRenderer->endPrecaching();
    //说明Batches数组是空,不需要刷新
    if (isEmpty()) return; 
    renderer.restoreToCount(1);
    // save and restore so that reordering doesn't affect final state
    renderer.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
    if (CC_LIKELY(mAvoidOverdraw)) {
        for (unsigned int i = 1; i < mBatches.size(); i++) {
            if (mBatches[i] && mBatches[i]->coversBounds(mBounds)) {
                //为避免过度绘制放弃一些不需要的mBatches元素。
                discardDrawingBatches(i - 1);
            }
        }
    }
    replayBatchList(mBatches, renderer, dirty);
    renderer.restoreToCount(1);
    clear();
}

DeferredDisplayList#replayBatchList方法负责刷新Batches数组。

static void replayBatchList(const Vector<Batch*>& batchList,
        OpenGLRenderer& renderer, Rect& dirty) {
    for (unsigned int i = 0; i < batchList.size(); i++) {
        if (batchList[i]) {
            batchList[i]->replay(renderer, dirty, i);
        }
    }
}

遍历数组每一项Batch。
DrawBatch#reply方法。

virtual void replay(OpenGLRenderer& renderer, Rect& dirty, int index) override {
    for (unsigned int i = 0; i < mOps.size(); i++) {
        DrawOp* op = mOps[i].op;
        const DeferredDisplayState* state = mOps[i].state;
        renderer.restoreDisplayState(*state);
        op->applyDraw(renderer, dirty);
        ...
    }
}

Batch中OpStatePair数组,遍历每一项OpStatePair。
从OpStatePair中获取DrawOp和DeferredDisplayState,触发DrawOp的applyDraw方法。
DrawOp的子类DrawRectsOp#applyDraw方法。

virtual void applyDraw(OpenGLRenderer& renderer, Rect& dirty) override {
    renderer.drawRects(mRects, mCount, mPaint);
}

最终,触发OpenGLRenderer的drawXxx方法。


任重而道远

相关文章

  • 硬件渲染-绘制一帧

    继续分析绘制一帧的相关内容。 绘制一帧 从CanvasContext#draw方法开始。 CanvasContex...

  • iOS中的绘制和渲染

    iOS的渲染和绘制机制 显示器原理和技术 电子枪逐行扫描(HSync),一帧画面绘制完成后,复原准备下一帧(VSy...

  • Android界面性能优化必读

    一. Android渲染知识 1.1 绘制原理 Android系统要求每一帧都要在 16ms 内绘制完成,平滑的完...

  • webview内核

    一、webView绘制相关 渲染方式至于WebView内部所使用的Chromium实现是采用硬件加速渲染还是软件渲...

  • Flutter skia

    CPU 渲染称之为软件绘制(关闭硬件加速时使用该引擎,开启硬件加速时使用OpenGL), Android CPU ...

  • Android开发性能优化

    1.布局优化 1.Android UI渲染机制16ms间隔,刷新一帧,使用“开发者选项”中gpu渲染,查看过度绘制...

  • Android UI布局,优化过度绘制

    优化前: 如何发现和监控XML的过度渲染和绘制? 设置—>开发人员选项—>硬件—>调试GPU过度绘制,然后选择 显...

  • iOS 核心动画 - 专用图层

    CAShapeLayer 优点 渲染快速。CAShapeLayer使用了硬件加速,绘制同一图形会比用Core Gr...

  • OpenGL ES渲染流程

    OpenGL ES作为一个底层渲染的接口,通过硬件加速,可以更好的处理图像绘制功能。 OpenGL ES渲染的过程...

  • 为什么每一帧都清除画布会提高性能?

    为什么每一帧都清除画布会提高性能? 是否需要glClear 在绝大多数的渲染代码中,都会在每一帧绘制前去清空画布(...

网友评论

      本文标题:硬件渲染-绘制一帧

      本文链接:https://www.haomeiwen.com/subject/ewunoftx.html