美文网首页
硬件渲染-数据同步

硬件渲染-数据同步

作者: gczxbb | 来源:发表于2018-07-10 17:47 被阅读89次

    Java层,ThreadedRenderer的draw方法,首先记录树形结构每个视图节点绘制操作,底层DisplayListData保存,然后JNI#方法,nSyncAndDrawFrame同步。
    根据指针,找到底层RenderProxy代理。
    RenderProxy#syncAndDrawFrame方法。

    int RenderProxy::syncAndDrawFrame() {
        return mDrawFrameTask.drawFrame();
    }
    

    DrawFrameTask任务,绘制帧


    唤醒渲染线程

    int DrawFrameTask::drawFrame() {
        mSyncResult = kSync_OK;
        mSyncQueued = systemTime(CLOCK_MONOTONIC);
        postAndWait();
        return mSyncResult;
    }
    

    主线程等待wait。

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

    渲染线程,RenderThread继承Thread,单例,内部任务队列TaskQueue,封装RenderTask任务操作。DrawFrameTask继承RenderTask。

    void RenderThread::queue(RenderTask* task) {
        AutoMutex _lock(mLock);
        mQueue.queue(task);
        if (mNextWakeup && task->mRunAt < mNextWakeup) {
            mNextWakeup = 0;
            mLooper->wake();
        }
    }
    

    插入队列TaskQueue,RenderThread的queue方法。判断下次唤醒的时间。唤醒渲染线程。
    渲染线程利用Looper休眠。

    RenderThread::RenderThread() : Thread(true), Singleton<RenderThread>()
        ... {
        mFrameCallbackTask = new DispatchFrameCallbacks(this);
        mLooper = new Looper(false);
        run("RenderThread");
    }
    

    构造方法,Looper创建,线程启动,threadLoop循环。

    bool RenderThread::threadLoop() {
        setpriority(PRIO_PROCESS, 0, PRIORITY_DISPLAY);
        initThreadLocals();
        int timeoutMillis = -1;
        for (;;) {//循环
            int result = mLooper->pollOnce(timeoutMillis);
            nsecs_t nextWakeup;
            while (RenderTask* task = nextTask(&nextWakeup)) {
                task->run();
            }
            if (nextWakeup == LLONG_MAX) {
                timeoutMillis = -1;
            } else {
                nsecs_t timeoutNanos = nextWakeup - systemTime(SYSTEM_TIME_MONOTONIC);
                timeoutMillis = nanoseconds_to_milliseconds(timeoutNanos);
                if (timeoutMillis < 0) {
                    timeoutMillis = 0;
                }
            }
            ...
        }
        return false;
    }
    

    渲染线程在Looper#pollOnce处休眠,当主线程插入任务时,Looper#wake唤醒渲染线程。
    队列的RenderTask任务依次执行。

    总结:
    渲染线程专注于与gpu交互,而主线程负责App视图绘制记录,在App,每一次树形视图绘制都是一个渲染任务。
    那么,这一次渲染任务到底做什么呢?


    一次渲染任务
    渲染线程,两个功能。
    1:同步帧状态,syncFrameState。
    2:绘制,draw。

    void DrawFrameTask::run() {
        bool canUnblockUiThread;
        bool canDrawThisFrame;
        {
            TreeInfo info(TreeInfo::MODE_FULL, mRenderThread->renderState());
            canUnblockUiThread = syncFrameState(info);
            canDrawThisFrame = info.out.canDrawThisFrame;
        }
        //任务内部的CanvasContext
        CanvasContext* context = mContext;
        if (canUnblockUiThread) {//唤醒主线程
            unblockUiThread();
        }
        if (CC_LIKELY(canDrawThisFrame)) {
            context->draw();
        }
        if (!canUnblockUiThread) {
            unblockUiThread();
        }
    }
    
    整流程图如下所示。 硬件渲染一次任务整体流程图.png

    功能点已在图中标记,下面分别分析。


    同步帧状态

    一个任务,就是一帧数据,syncFrameState方法同步帧的状态和数据。

    bool DrawFrameTask::syncFrameState(TreeInfo& info) {
        int64_t vsync = mFrameInfo[static_cast<int>(FrameInfoIndex::Vsync)];
        mRenderThread->timeLord().vsyncReceived(vsync);
        mContext->makeCurrent();
        Caches::getInstance().textureCache.resetMarkInUse(mContext);
        //第一步,遍历DeferredLayerUpdater数组Vector
        //触发CanvasContext的processLayerUpdate方法。
        for (size_t i = 0; i < mLayers.size(); i++) {
            mContext->processLayerUpdate(mLayers[i].get());
        }
        //每次push结束后清理数组
        mLayers.clear();
        //第二步
        mContext->prepareTree(info, mFrameInfo, mSyncQueued);
        if (info.out.hasAnimations) {
            if (info.out.requiresUiRedraw) {
                mSyncResult |= kSync_UIRedrawRequired;
            }
        }
        //如果返回的是false,说明我们耗尽了textureCache的空间
        return info.prepareTextures;
    }
    

    第一步 遍历Layers数组

    分析一下什么样的视图会将Layer加入DrawFrameTask的Layers数组?

    DrawFrameTask#pushLayerUpdate负责添加元素。追溯到Java层。
    ThreadedRenderer#pushLayerUpdate方法。

    @Override
    void pushLayerUpdate(HardwareLayer layer) {
        nPushLayerUpdate(mNativeProxy, layer.getDeferredLayerUpdater());
    }
    

    通过JNI#方法,触发RenderProxy#pushLayerUpdate方法。

    void RenderProxy::pushLayerUpdate(DeferredLayerUpdater* layer) {
        mDrawFrameTask.pushLayerUpdate(layer);
    }
    

    因此,向Layers数组添加元素,由Java层ThreadedRenderer的pushLayerUpdate发起的。那么,这是什么样的视图呢?
    以下两个HardwareLayer#均触发ThreadedRenderer#pushLayerUpdate方法。**

    public void setSurfaceTexture(SurfaceTexture surface) {
        nSetSurfaceTexture(mFinalizer.get(), surface, false);
        mRenderer.pushLayerUpdate(this);//mRenderer即ThreadedRenderer。
    }
    
    public void updateSurfaceTexture() {
        nUpdateSurfaceTexture(mFinalizer.get());
        mRenderer.pushLayerUpdate(this);
    }
    

    TextureView视图有一个HardwareLayer,HardwareLayer对应底层DeferredLayerUpdater,封装真正的Layer,因此,可以得出结论。

    TextureView视图刷新时,利用ThreadedRenderer将HardwareLayer底层DeferredLayerUpdater加入DrawFrameTask的数组,第一步遍历数组针对TextureView视图。
    普通视图,数组是空。

    针对TextureView视图处理

    遍历每一个DeferredLayerUpdater,CanvasContext#processLayerUpdate方法。

    void CanvasContext::processLayerUpdate(DeferredLayerUpdater* layerUpdater) {
        bool success = layerUpdater->apply();
        if (layerUpdater->backingLayer()->deferredUpdateScheduled) {
            //mCanvas是OpenGLRenderer。
            mCanvas->pushLayerUpdate(layerUpdater->backingLayer());
        }
    }
    

    HardwareLayer结构图如下。

    HardwareLayer结构图.jpg 由代码可知,将deferredUpdateScheduled标志的Layer加入OpenGLRenderer的Layer数组
    此标志从哪里来呢?TextureView视图的Layer有此标志吗?

    先看一下LayerType。上层配置三种。
    LAYER_TYPE_NONE。
    LAYER_TYPE_SOFTWARE。
    LAYER_TYPE_HARDWARE。
    底层对应None = 0,Software = 1,RenderLayer = 2。
    View#setLayerType设置LayerType,TextureView重写此方法,忽略类型,默认不需要,LAYER_TYPE_NONE。

    从源码中可知,Layer#updateDeferred方法设置deferredUpdateScheduled标志,Layer渲染后重置。此方法仅在RenderNode#pushLayerUpdate中调用,由RenderNode内部Layer触发。
    LayerType是RenderLayer。

    因此:上层专门设置LAYER_TYPE_HARDWARE的视图才会在RenderNode内部创建Layer,并有此标志。目前看来,TextureView视图底层Layer没有deferredUpdateScheduled标志,不会被加入OpenGLRenderer的Layer数组。

    总结

    第一步,处理和TextureView视图相关,底层Layer,普通视图无HardwareLayer,因此,DrawFrameTask数组是空。


    第二步 准备ViewTree的数据

    void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, 
                    int64_t syncQueued) {
        mRenderThread.removeFrameCallback(this);
        //当前帧FrameInfo无SkippedFrame标志
        //说明之前的帧没有被跳过,mCurrentFrameInfo指向缓冲区新节点。
        if (!wasSkipped(mCurrentFrameInfo)) {
            mCurrentFrameInfo = &mFrames.next();//RingBuffer<FrameInfo, 120>循环缓冲区
        }
        //若之前的帧被跳过,继续用当前指向的节点进行初始化。
        mCurrentFrameInfo->importUiThreadInfo(uiFrameInfo);
        mCurrentFrameInfo->set(FrameInfoIndex::SyncQueued) = syncQueued;
        mCurrentFrameInfo->markSyncStart();
        //初始化info信息
        info.damageAccumulator = &mDamageAccumulator;
        info.renderer = mCanvas;
        info.canvasContext = this;
        mAnimationContext->startFrame(info.mode);
        //底层根节点RenderNode准备,树形结构遍历
        mRootRenderNode->prepareTree(info);
        mAnimationContext->runRemainingAnimations(info);
        //不存在mNativeWindow退出,无法绘制该帧canDrawThisFrame设false
        int runningBehind = 0;
        mNativeWindow->query(mNativeWindow.get(),
                NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &runningBehind);
        info.out.canDrawThisFrame = !runningBehind;
        if (!info.out.canDrawThisFrame) {
            mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
        }
        if (info.out.hasAnimations || !info.out.canDrawThisFrame) {
            if (!info.out.requiresUiRedraw) {
                mRenderThread.postFrameCallback(this);
            }
        }
    }
    

    CanvasContext的底层根节点RootRenderNode,在CanvasContext构造方法初始化。在上层ThreadedRenderer保存指针,RootRenderNode继承RenderNode。

    void RenderNode::prepareTree(TreeInfo& info) {
        bool functorsNeedLayer = Properties::debugOverdraw;
        prepareTreeImpl(info, functorsNeedLayer);
    }
    

    从根节点开始,递归遍历。

    void RenderNode::prepareTreeImpl(TreeInfo& info, bool functorsNeedLayer) {
        info.damageAccumulator->pushTransform(this);
        //还有一种模式MODE_RT_ONLY
        if (info.mode == TreeInfo::MODE_FULL) {
            //属性改变,赋值给mProperties。
            pushStagingPropertiesChanges(info);
        }
        bool willHaveFunctor = false;
        if (info.mode == TreeInfo::MODE_FULL && mStagingDisplayListData) {
            willHaveFunctor = !mStagingDisplayListData->functors.isEmpty();
        } else if (mDisplayListData) {
            willHaveFunctor = !mDisplayListData->functors.isEmpty();
        }
        bool childFunctorsNeedLayer = mProperties.prepareForFunctorPresence(
                willHaveFunctor, functorsNeedLayer);
        prepareLayer(info, animatorDirtyMask);
        if (info.mode == TreeInfo::MODE_FULL) {
            //赋值给mDisplayListData。
            pushStagingDisplayListChanges(info);
        }
        prepareSubTree(info, childFunctorsNeedLayer, mDisplayListData);
        pushLayerUpdate(info);
    
        info.damageAccumulator->popTransform();
    }
    

    prepareTree准备树形结构的流程如下图所示。

    CanvasContext的prepareTree准备树形结构的流程.jpg 关注两个方法。
    prepareSubTree方法,递归遍历子节点。
    pushLayerUpdate方法,RenderNode节点的内部Layer处理。
    void RenderNode::prepareSubTree(TreeInfo& info, bool functorsNeedLayer, 
                    DisplayListData* subtree) {
        //该节点的DisplayListData存在
        if (subtree) {
            //获取TextureCahe对象
            TextureCache& cache = Caches::getInstance().textureCache;
            //数据中Functor集合
            info.out.hasFunctors |= subtree->functors.size();
            for (size_t i = 0; info.prepareTextures && i < 
                            subtree->bitmapResources.size(); i++) {
                //若有一个texture失败,是0,赋值prepareTextures,不再遍历。
                info.prepareTextures = cache.prefetchAndMarkInUse(
                        info.canvasContext, subtree->bitmapResources[i]);
            }
            for (size_t i = 0; i < subtree->children().size(); i++) {
                DrawRenderNodeOp* op = subtree->children()[i];
                RenderNode* childNode = op->mRenderNode;
                info.damageAccumulator->pushTransform(&op->mTransformFromParent);
                bool childFunctorsNeedLayer = functorsNeedLayer
                        || op->mRecordedWithPotentialStencilClip;
                childNode->prepareTreeImpl(info, childFunctorsNeedLayer);
                info.damageAccumulator->popTransform();
            }
        }
    }
    
    • 遍历SkBitmap数组,查看每个Bitmap是否准备好纹理。

    SkBitmap数组元素从哪里来呢?

    画布绘制Bitmap,DisplayListCanvas#drawBitmap方法。

    void DisplayListCanvas::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) {
        //refBitmap会向mDisplayListData的bitmapResources数组中保存一份localBitmap
        bitmap = refBitmap(*bitmap); 
        paint = refPaint(paint);
        addDrawOp(new (alloc()) DrawBitmapOp(bitmap, paint));
    }
    
    inline const SkBitmap* refBitmap(const SkBitmap& bitmap) {
        SkBitmap* localBitmap = new (alloc()) SkBitmap(bitmap);
        alloc().autoDestroy(localBitmap);
        mDisplayListData->bitmapResources.push_back(localBitmap);
        return localBitmap;
    }
    

    增加一个DrawBitmapOp操作,向数组保存一个SkBitmap。

    • prefetchAndMarkInUse方法,返回Bitmap的Texture。
      根据piexlRef的stableId在LruCache缓存查找Texture,若未查到,判断是否可创建,若加上Bitmap大小后大于缓存设计的大小,删除未使用的旧Texture,若仍大于最大值,无法创建新Texture,返回null。
      创建Texture时,先glGenTextures,生成纹理对象索引,保存在texture的id,bindTexture绑定,glTexImage2D生成2D纹理将绑定Bitmap像素地址。最后,新Texture存入LruCache缓存。
    bool TextureCache::prefetchAndMarkInUse(void* ownerToken, const SkBitmap* bitmap) {
        Texture* texture = getCachedTexture(bitmap, AtlasUsageType::Use);
        if (texture) {//texture不为0
            texture->isInUse = ownerToken;
        }
        return texture;
    }
    

    若所有Texture存在,在syncFrameState结束后,返回info.prepareTextures标志true,它会影响唤醒主线程的时机。

    • 遍历DisplayListData的DrawRenderNodeOp数组。底层子RenderNode节点。递归prepareTreeImpl方法。

    DrawRenderNodeOp数组是如何加入的呢?

    追溯到Java层,DisplayListCanvas#drawRenderNode方法将子节点RenderNode写入。底层创建一个DrawRenderNodeOp操作,封装被绘制视图底层RenderNode节点,将操作加入到DrawRenderNodeOp数组。

    RenderNode#pushLayerUpdate方法。

    void RenderNode::pushLayerUpdate(TreeInfo& info) {
        LayerType layerType = properties().effectiveLayerType();
        if (CC_LIKELY(layerType != LayerType::RenderLayer) || 
                    CC_UNLIKELY(!isRenderable())) {
            if (CC_UNLIKELY(mLayer)) {
                LayerRenderer::destroyLayer(mLayer);
                mLayer = nullptr;
            }
            return;
        }
        bool transformUpdateNeeded = false;
        if (!mLayer) {
            //内部Layer不存在,由LayerRenderer创建。
            mLayer = LayerRenderer::createRenderLayer(info.renderState, 
                    getWidth(), getHeight());
            applyLayerPropertiesToLayer(info);
            damageSelf(info);
            transformUpdateNeeded = true;
        }
        ...
        if (dirty.intersect(0, 0, getWidth(), getHeight())) {
            dirty.roundOut(&dirty);
            //初始化Layer的RenderNode,deferredUpdateScheduled标志
            //初始化渲染器render为LayerRenderer。
            mLayer->updateDeferred(this, dirty.fLeft, dirty.fTop, 
                    dirty.fRight, dirty.fBottom);
        }
        if (info.renderer && mLayer->deferredUpdateScheduled) {
            info.renderer->pushLayerUpdate(mLayer);//push进数组
        }
        ...
    }
    

    满足两个条件执行。
    LayerType必须是RenderLayer类型(Java层,View#setLayerType方法设置)。
    DisplayListData不是空。
    否则退出方法。
    初始化创建RenderNode内部Layer,TreeInfo的OpenGLRenderer,将该Layer加入OpenGLRenderer的Layer数组,等待OpenGLRenderer#updateLayers更新。

    底层RenderNode结构图如下。 底层RenderNode结构图.jpg
    总结

    1,底层根节点RootRenderNode开始,执行prepareTreeImpl方法,prepareSubTree进入子视图,从当前RenderNode的DisplayListData中查找子视图RenderNode节点,递归底层子视图节点RenderNode的prepareTreeImpl方法。
    2,底层根节点RootRenderNode并非顶层视图节点,而是上层ThreadedRenderer内部RenderNode对应的底层节点。
    3,上层画布触发过drawBitmap时,存在SkBitmap数组。
    4,上层设置LAYER_TYPE_HARDWARE的视图,底层RenderNode内部创建一个Layer。

    到这里,就结束了根RenderNode的prepareTreeImpl方法,并递归遍历子节点的prepareTreeImpl。继续回到CanvasContext的prepareTree。
    接下来是NativeWindow#query方法。
    那么何时初始化NativeWindow呢?

    追溯到Java层,ViewRootImpl#performTraversals,调用ThreadedRenderer的initialize方法,该方法传入由Wms初始化的Surface。

    @Override
    boolean initialize(Surface surface) throws OutOfResourcesException {
        mInitialized = true;
        updateEnabledState(surface);
        boolean status = nInitialize(mNativeProxy, surface);
        return status;
    }
    

    根据上层Surface获取底层NativeWindow。从JNI#触发RenderProxy#initialize方法,到CanvasContext的initialize方法。

    bool CanvasContext::initialize(ANativeWindow* window) {
        setSurface(window);
        mCanvas = new OpenGLRenderer(mRenderThread.renderState());
        mCanvas->initProperties();
        return true;
    }
    

    初始化OpenGLRenderer。
    利用NativeWindow创建一个mEglSurface。

    void CanvasContext::setSurface(ANativeWindow* window) {
        mNativeWindow = window;
        ...
        if (window) {
            mEglSurface = mEglManager.createSurface(window);
        }
    }
    

    NativeWindow是Java层Surface的底层对象
    query方法的目的是查询生产者和消费者缓冲区的运行情况。

    NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND的官方解释为是否消费者比生产者对缓冲区的处理落后大于1个缓冲区,runningBehind返回结果,如果runningBehind为false说明未落后,则info.out.canDrawThisFrame置为true,说明该帧可以绘制,否则canDrawThisFrame设为false跳过该帧。

    总结:

    第二步,prepareTree逻辑是绘制准备,将属性与DisplayList数据交接,DrawRenderNodeOp操作对应节点递归处理,RenderNode必要时初始化Layer,准备NativeWindow查询是否该帧需要跳过。
    syncFrameState同步结束,返回info.prepareTextures结果,赋值canUnblockUiThread。
    若info.prepareTextures标志,唤醒主线程。Signal#signal()。
    否则,说明存在未准备好的Texture。先执行CanvasContext#draw方法,再唤醒主线程。


    任重而道远

    相关文章

      网友评论

          本文标题:硬件渲染-数据同步

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