RenderNode Demo

Demo

int left = 0;
int right = 640;
int top = 0;
int bottom = 480;
sp<Surface> surface = xxxx;

//创建RenderNode
sp<RenderNode> node = new RenderNode();
RenderProperties& props = node->mutateStagingProperties();
props.setLeftTopRightBottom(left, top, right, bottom);
node->setPropertyFieldsDirty(0xFFFFFFFF);

 std::unique_ptr<Canvas> canvas(Canvas::create_recording_canvas(width, height));
//canvas绘制
...
node->setStagingDisplayList(canvas.finishRecording());
node->setPropertyFieldsDirty(0xFFFFFFFF);

//创建RenderProxy
ContextFactory factory;
std::unique_ptr<RenderProxy> proxy(new RenderProxy(false, rootNode.get(), &factory));
proxy->loadSystemProperties();
proxy->setSurface(surface.get());
//设置绘制边距,即绘制区域大小
proxy->setContentDrawBounds(left, top, right, bottom);

proxy->syncAndDrawFrame();
proxy->resetProfileInfo();
proxy->fence();

一、帧绘制流程

1、RenderProxy::syncAndDrawFrame

实际调用的是DrawFrameTask::drawFrame()方法。

//hwui/renderthread/RenderProxy.cpp
int RenderProxy::syncAndDrawFrame() {
    return mDrawFrameTask.drawFrame();
}

2、DrawFrameTask::drawFrame

最终调用到DrawFrameTask::run方法,在run方法内调用CanvasContext::draw方法。

//hwui/rednerthread/DrawFrameTask.cpp
int DrawFrameTask::drawFrame() {
    mSyncResult = SyncResult::OK;
    postAndWait();
    return mSyncResult;
}

void DrawFrameTask::postAndWait() {
    AutoMutex _lock(mLock);
    mRenderThread->queue().post([this]() { run(); });
    mSignal.wait(mLock);
}

void DrawFrameTask::run() {
    CanvasContext* context = mContext;
    dequeueBufferDuration = context->draw();
}

3、CanvsContext::draw

CanvasContext::draw方法将自己的RenderNode数组传递给SkiaOpenGLPipline进行绘制,待绘制完成后,调用其swapBuffers进行送显。

//hwui/renderthread/CanvasContext.cpp
nsecs_t CanvasContext::draw() {
    SkRect dirty;
    Frame frame = mRenderPipeline->getFrame();
    SkRect windowDirty = computeDirtyRect(frame, &dirty);
    bool drew = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue,
                                      mContentDrawBounds, mOpaque, mLightInfo, mRenderNodes,
                                      &(profiler()));
    waitOnFences();
    bool didSwap = mRenderPipeline->swapBuffers(frame, drew, windowDirty, mCurrentFrameInfo, &requireSwap);
    mIsDirty = false;
}    

4、SkiaOpenGLPipeline::draw(真正开始绘制)

创建SkSurface,并获取SkCanvas对象,绘制的准备工作完成。然后使用Render、SkCanvas构建RenderNodeDrawable对象,并调用其draw方法进行绘制。

//hwui/pipline/skia/SkiaOpenGLPipline.cpp
bool SkiaOpenGLPipeline::draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
                              const LightGeometry& lightGeometry,
                              LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds,
                              bool opaque, const LightInfo& lightInfo,
                              const std::vector<sp<RenderNode>>& renderNodes,
                              FrameInfoVisualizer* profiler) {
    SkColorType colorType = getSurfaceColorType();
    // setup surface for fbo0
    GrGLFramebufferInfo fboInfo;
    fboInfo.fFBOID = 0;
    // Note: The default preference of pixel format is RGBA_8888, when other
    // pixel format is available, we should branch out and do more check.
    fboInfo.fFormat = GL_RGBA8;

    GrBackendRenderTarget backendRT(frame.width(), frame.height(), 0, STENCIL_BUFFER_SIZE, fboInfo);
    SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
    sk_sp<SkSurface> surface(SkSurface::MakeFromBackendRenderTarget(mRenderThread.getGrContext(), backendRT, this->getSurfaceOrigin(), colorType, mSurfaceColorSpace, &props));

    LightingInfo::updateLighting(lightGeometry, lightInfo);
    //调用renderFrame进行渲染
    renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface, SkMatrix::I());
                
    surface->flushAndSubmit();
    layerUpdateQueue->clear();
    return true;
}

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) {
    // Initialize the canvas for the current frame, that might be a recording canvas if SKP
    // capture is enabled.
    SkCanvas* canvas = tryCapture(surface.get(), nodes[0].get(), layers);
    // draw all layers up front
    renderLayersImpl(layers, opaque);
    renderFrameImpl(clip, nodes, opaque, contentDrawBounds, canvas, preTransform);
    endCapture(surface.get());

    if (CC_UNLIKELY(Properties::debugOverdraw)) {
        renderOverdraw(clip, nodes, contentDrawBounds, surface, preTransform);
    }
}

void SkiaPipeline::renderFrameImpl(const SkRect& clip,
                                   const std::vector<sp<RenderNode>>& nodes, bool opaque,
                                   const Rect& contentDrawBounds, SkCanvas* canvas,
                                   const SkMatrix& preTransform) {
    SkAutoCanvasRestore saver(canvas, true);
    canvas->concat(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 {
        // It there are multiple render nodes, they are laid out as follows:
        // #0 - backdrop (content + caption)
        // #1 - content (local bounds are at (0,0), will be translated and clipped to backdrop)
        // #2 - additional overlay nodes
        // Usually the backdrop cannot be seen since it will be entirely covered by the content.
        // While
        // resizing however it might become partially visible. The following render loop will crop
        // the
        // backdrop against the content and draw the remaining part of it. It will then draw the
        // content
        // cropped to the backdrop (since that indicates a shrinking of the window).
        //
        // Additional nodes will be drawn on top with no particular clipping semantics.

        // Usually the contents bounds should be mContentDrawBounds - however - we will
        // move it towards the fixed edge to give it a more stable appearance (for the moment).
        // If there is no content bounds we ignore the layering as stated above and start with 2.

        // Backdrop bounds in render target space
        const Rect backdrop = nodeBounds(*nodes[0]);

        // Bounds that content will fill in render target space (note content node bounds may be
        // bigger)
        Rect content(contentDrawBounds.getWidth(), contentDrawBounds.getHeight());
        content.translate(backdrop.left, backdrop.top);
        if (!content.contains(backdrop) && !nodes[0]->nothingToDraw()) {
            // Content doesn't entirely overlap backdrop, so fill around content (right/bottom)

            // Note: in the future, if content doesn't snap to backdrop's left/top, this may need to
            // also fill left/top. Currently, both 2up and freeform position content at the top/left
            // of
            // the backdrop, so this isn't necessary.
            RenderNodeDrawable backdropNode(nodes[0].get(), canvas);
            if (content.right < backdrop.right) {
                // draw backdrop to right side of content
                SkAutoCanvasRestore acr(canvas, true);
                canvas->clipRect(SkRect::MakeLTRB(content.right, backdrop.top, backdrop.right,
                                                  backdrop.bottom));
                backdropNode.draw(canvas);
            }
            if (content.bottom < backdrop.bottom) {
                // draw backdrop to bottom of content
                // Note: bottom fill uses content left/right, to avoid overdrawing left/right fill
                SkAutoCanvasRestore acr(canvas, true);
                canvas->clipRect(SkRect::MakeLTRB(content.left, content.bottom, content.right,
                                                  backdrop.bottom));
                backdropNode.draw(canvas);
            }
        }

        RenderNodeDrawable contentNode(nodes[1].get(), canvas);
        if (!backdrop.isEmpty()) {
            // content node translation to catch up with backdrop
            float dx = backdrop.left - contentDrawBounds.left;
            float dy = backdrop.top - contentDrawBounds.top;

            SkAutoCanvasRestore acr(canvas, true);
            canvas->translate(dx, dy);
            const SkRect contentLocalClip =
                    SkRect::MakeXYWH(contentDrawBounds.left, contentDrawBounds.top,
                                     backdrop.getWidth(), backdrop.getHeight());
            canvas->clipRect(contentLocalClip);
            contentNode.draw(canvas);
        } else {
            SkAutoCanvasRestore acr(canvas, true);
            contentNode.draw(canvas);
        }

        // remaining overlay nodes, simply defer
        for (size_t index = 2; index < nodes.size(); index++) {
            if (!nodes[index]->nothingToDraw()) {
                SkAutoCanvasRestore acr(canvas, true);
                RenderNodeDrawable overlayNode(nodes[index].get(), canvas);
                overlayNode.draw(canvas);
            }
        }
    }
}

5、RenderNodeDrawable::onDraw

从RenderNode中获取DisplayList,最终会调用的DisplayList::draw方法。

//hwui/pipeline/skia/RenderNodeDrawable.cpp
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);
    }
}

void RenderNodeDrawable::forceDraw(SkCanvas* canvas) const {
    RenderNode* renderNode = mRenderNode.get();
    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) {
            canvas->setMatrix(mProjectedDisplayList->mParentMatrix);
            drawBackwardsProjectedNodes(canvas, *mProjectedDisplayList);
        }
    }
    displayList->mProjectedOutline = nullptr;
}

void RenderNodeDrawable::drawContent(SkCanvas* canvas) const {
    RenderNode* renderNode = mRenderNode.get();
    SkiaDisplayList* displayList = mRenderNode->getDisplayList().asSkiaDl();
    displayList->mParentMatrix = canvas->getTotalMatrix();
	TransformCanvas transformCanvas(canvas, SkBlendMode::kClear);
	displayList->draw(&transformCanvas);
}

6、DisplayList::draw

DisplayList::draw实际上调用的是DisplayList::draw方法,内部调用map方法,依次执行添加DisplayListData中的任务。

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

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;
    }
}

二、RenderNode传递过程

1、SkiaRecordingCanvas

该类的接口几乎与Canvas对齐,但执行drawLine、drawRect时并没有直接的绘制,而是将绘制生成一个个的绘制命令存储在DisplayList。

// hwui/pipeline/skia/SkiaRecordingCanvas.cpp
std::unique_ptr<SkiaDisplayList> SkiaRecordingCanvas::finishRecording() {
    // close any existing chunks if necessary
    enableZ(false);
    mRecorder.restoreToCount(1);
    return std::move(mDisplayList);
}
//直接将DisplayList存储到RenderNode中
void SkiaRecordingCanvas::finishRecording(uirenderer::RenderNode* destination) {
    destination->setStagingDisplayList(uirenderer::DisplayList(finishRecording()));
}

2、RenderNode

  • RenderNode内部存储DisplayList对象,由setStagingDisplayList方法接受外部的DisplayList数据
  • DisplayList在RenderNodeDrawable::drawContent中取出使用
// hwui/RenderNode.cpp
void RenderNode::setStagingDisplayList(DisplayList&& newData) {
    mValid = newData.isValid();
    mNeedsDisplayListSync = true;
    mStagingDisplayList = std::move(newData);
}

void RenderNode::syncDisplayList(TreeObserver& observer, TreeInfo* info) {
    // Make sure we inc first so that we don't fluctuate between 0 and 1,
    // which would thrash the layer cache
    if (mStagingDisplayList) {
        mStagingDisplayList.updateChildren([](RenderNode* child) { child->incParentRefCount(); });
    }
    deleteDisplayList(observer, info);
    //将mStagingDisplayList暂存数据同步到mDisplayList中
    mDisplayList = std::move(mStagingDisplayList);
    if (mDisplayList) {
        WebViewSyncData syncData {
            .applyForceDark = info && !info->disableForceDark
        };
        mDisplayList.syncContents(syncData);
        handleForceDark(info);
    }
}
  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
你可以使用递归算法来遍历 jqgrid tree,按照层级顺序渲染。具体实现方法如下: 1. 首先,你需要确定 jqgrid tree 的数据结构。通常情况下,它是一个嵌套的 JSON 数组,每个节点都有一个唯一的 id 和一个 parent_id,指向它的父节点。 2. 接下来,你可以定义一个递归函数,用来遍历 jqgrid tree。该函数接受一个节点作为参数,并将该节点渲染到页面上。 3. 在递归函数中,你可以先渲染当前节点,然后递归遍历该节点的所有子节点。你可以使用 jQuery 的 each() 函数来遍历子节点,然后对每个子节点递归调用该函数。 4. 在渲染节点时,你可以根据节点的层级来设置缩进,以便更清晰地显示节点之间的层级关系。你可以使用 CSS 的 padding 属性来设置缩进。 下面是一个简单的示例代码,用来遍历 jqgrid tree 并按照层级顺序渲染: ```javascript function renderNode(node, level) { // 渲染当前节点 var html = '<tr><td style="padding-left: ' + level * 20 + 'px;">' + node.name + '</td></tr>'; $("#grid").append(html); // 遍历子节点 $.each(node.children, function(index, child) { renderNode(child, level + 1); }); } // 遍历 jqgrid tree $.each(data, function(index, node) { if (node.parent_id == null) { renderNode(node, 0); } }); ``` 在上面的示例代码中,renderNode() 函数用来渲染节点,它接受两个参数:节点对象和节点的层级。在遍历 jqgrid tree 时,我们使用 each() 函数来遍历每个根节点,并调用 renderNode() 函数来渲染整个树形结构。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值