RenderThread学习笔记

文章记录自己对RenderThread学习过程,供日后回顾。

我们知道5.0上为每个进程新增了一个RenderThread线程,这是一个附加的UI线程?我们通过学习这块代码来熟悉它。

第一部分:RenderThread线程的启动

RenderThread线程的启动过程如下图所示。

RenderThread学习笔记_第1张图片

涉及相关的类关系如下图:

RenderThread学习笔记_第2张图片

有两个注意点:①每一个窗口对应一个ViewRootImpl,每个ViewRootImpl都对应唯一的一个ThreadedRenderer、一个RootRenderNode、一个RenderProxy对象。②一个RenderProxy对象又对应一个CanvasContext对象。③一个CanvasContext又对应一个OpenGLRenderer对象。④一个拥有窗口的进程,必然有且只有一个RenderThread子线程。


第二部分:RenderThread.threadLoop()分析

代码在 frameworks\base\libs\hwui\renderthread\RenderThread.cpp中。

bool RenderThread::threadLoop() {
    setpriority(PRIO_PROCESS, 0, PRIORITY_DISPLAY);
    initThreadLocals();          //初始化必要的环境信息。

    int timeoutMillis = -1;
    for (;;) {
        int result = mLooper->pollOnce(timeoutMillis);
        LOG_ALWAYS_FATAL_IF(result == Looper::POLL_ERROR,
                "RenderThread Looper POLL_ERROR!");

        nsecs_t nextWakeup;
        // Process our queue, if we have anything
        while (RenderTask* task = nextTask(&nextWakeup)) {      //这个while循环会将当前时间点及之前的所有本该触发调用的RenderTask全部执行run()。nextWakeup为下一个RenderTask的触发调用时间点。
            task->run();
            // task may have deleted itself, do not reference it again
        }
        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;
            }
        }

        if (mPendingRegistrationFrameCallbacks.size() && !mFrameCallbackTaskPending) {  
            drainDisplayEventQueue();                  
            mFrameCallbacks.insert(
                    mPendingRegistrationFrameCallbacks.begin(), mPendingRegistrationFrameCallbacks.end());
            mPendingRegistrationFrameCallbacks.clear();
            requestVsync();
        }

        if (!mFrameCallbackTaskPending && !mVsyncRequested && mFrameCallbacks.size()) {
            // TODO: Clean this up. This is working around an issue where a combination
            // of bad timing and slow drawing can result in dropping a stale vsync
            // on the floor (correct!) but fails to schedule to listen for the
            // next vsync (oops), so none of the callbacks are run.
            requestVsync();
        }
    }

    return false;
}


      mPendingRegistrationFrameCallbacks处理逻辑:DispatchFrameCallbacks::run() -->RenderThread::dispatchFrameCallbacks() -->CanvasContext::doFrame()
      mPendingRegistrationFrameCallbacks来源:CanvasContext::doFrame() -->CanvasContext::prepareTree() -->RenderThread::postFrameCallback() -->mPendingRegistrationFrameCallbacks.insert(callback)

      如果有有效的Vsync信号,那么添加一个mFrameCallbackTask到TaskQueue队列中;如果没有有效的Vsync信号,那么在下面调用requestVsync()后,Vsync信号上来后会触发调用drainDisplayEventQueue()添加mFrameCallbackTask到TaskQueue队列中。mFrameCallbackTask这个RenderTask用来触发调用mPendingRegistrationFrameCallbacks的doFrame()函数,进一步分析发现mPendingRegistrationFrameCallbacks就是指向CanvasContext,也就是说mFrameCallbackTask会触发调用CanvasContext::doFrame()。

      结合上面所说的mPendingRegistrationFrameCallbacks来源流程可以清楚的知道mFrameCallbackTask就是用来做动画的。

      initThreadLocals()主要工作包括与SurfaceFlinger的EventThread线程建立起连接、构建出一个接收Vsync信号框架、通过Looper.addFd()添加一个Vsync信号处理函数RenderThread::displayEventReceiverCallback()等工作。调用逻辑如下图:

RenderThread学习笔记_第3张图片


      接收到Vsync信号,就会调用displayEventReceiverCallback静态函数,该函数继续调用drainDisplayEventQueue()。

void RenderThread::drainDisplayEventQueue() {
    ATRACE_CALL();
    nsecs_t vsyncEvent = latestVsyncEvent(mDisplayEventReceiver);        //将最新的Vsync信号读出来,大于0则有效
    if (vsyncEvent > 0) {                                               
        mVsyncRequested = false;                                     //mVsyncRequested置为false表示前面request的Vsync信号已经接收到了
        if (mTimeLord.vsyncReceived(vsyncEvent) && !mFrameCallbackTaskPending) {
            ATRACE_NAME("queue mFrameCallbackTask");
            mFrameCallbackTaskPending = true;                           //mFrameCallbackTaskPending置为true表示当前已经加入一个mFrameCallbackTask型的RenderTask到队列中等待处理
            nsecs_t runAt = (vsyncEvent + DISPATCH_FRAME_CALLBACKS_DELAY);
            queueAt(mFrameCallbackTask, runAt);                         //添加一个mFrameCallbackTask到队列中,这个是用来做动画的
        }
    }
}

上面涉及相关的类关系如下:

RenderThread学习笔记_第4张图片

从前面那个while循环可以看出RenderThread线程最核心的功能便是"到点触发"调用一个个小任务RenderTask。

借用老罗的总结:

Render Thread在运行时主要是做以下两件事情:

       1. 执行Task Queue的任务,这些Task一般就是由MainThread发送过来的,例如,MainThread通过发送一个DrawFrameTask给RenderThread的TaskQueue中,请求RenderThread渲染窗口的下一帧。

       2. 执行PendingRegistrationFrameCallbacks列表的IFrameCallback回调接口。每一个IFrameCallback回调接口代表的是一个动画帧,这些动画帧被同步到Vsync信号到来由RenderThread自动执行。具体来说,就是每当Vsync信号到来时,就将一个类型为DispatchFrameCallbacks的Task添加到RenderThread的TaskQueue去等待调度。一旦该Task被调度,就可以在RenderThread中执行注册在PendingRegistrationFrameCallbacks列表中的IFrameCallback回调接口了。

RenderProxy内部三个最重要的变量作用如下:

      1.mRenderThread,它指向的是一个RenderThread对象,通过它可以向RenderThread线程发送命令。

      2.mContext,它指向的是一个CanvasContext对象,RenderThread的渲染工作就是通过它来完成的。

      3.mDrawFrameTask,它指向的是一个DrawFrameTask对象,MainThread通过它向RenderThread线程发送渲染下一帧的命令。

第三部分:绑定窗口对应的Surface到RenderThread的过程

大体流程如下:

RenderThread学习笔记_第5张图片

借用老罗的总结:

  从这里就可以看到,将一个EGL Surface绑定到Render Thread的Open GL渲染上下文中是通过CanvasContext类的成员变量mEglManager指向的一个EglManager对象的成员函数makeCurrent来完成的。实际上就是通过EGL函数建立了从Open GL到底层OS图形系统的桥梁。这一点应该怎么理解呢?Open GL是一套与OS无关的规范,不过当它在一个具体的OS实现时,仍然是需要与OS的图形系统打交道的。例如,Open GL需要从底层的OS图形系统中获得图形缓冲区来保存渲染结果,并且也需要将渲染好的图形缓冲区交给底层的OS图形系统来显示到设备屏幕去。Open GL与底层的OS图形系统的这些交互通道都是通过EGL函数来建立的。

第四部分:draw过程

渲染过程大致如下:

RenderThread学习笔记_第6张图片

第四部分:DisplayListRenderer::addDrawOp()函数分析

    我们知道RenderNode通过start()来申请一个空闲的GLES20Canvas来进行渲染。GLES20Canvas.mRenderer指向native层的DisplayListRenderer对象,事实上GLES20Canvas的渲染工作都是由native层的DisplayListRenderer来完成。比如GLES20Canvas.drawBitmap()命令最终是调用DisplayListRenderer::drawBitmap(),而DisplayListRenderer::drawBitmap()会创建一个DrawOp,然后调用addDrawOp()添加到渲染命令集合DisplayListData中来。由此看来上层调用Canvas API绘制UI时,实际上只是将调用及参数保存在DisplayListData中,“然后等到下一个Vsync信号到来时,记录在Display List里面的绘制命令才会转化为Open GL命令由GPU执行。与直接执行绘制命令相比,先将绘制命令记录在Display List中然后再执行有两个好处。第一个好处是在绘制窗口的下一帧时,若某一个视图的UI没有发生变化,那么就不必执行与它相关的Canvas API,即不用执行它的成员函数onDraw,而是直接复用上次构建的Display List即可。第二个好处是在绘制窗口的下一帧时,若某一个视图的UI发生了变化,但是只是一些简单属性发生了变化,例如位置和透明度等简单属性,那么也不必重建它的Display List,而是直接修改上次构建的Display List的相关属性即可,这样也可以省去执行它的成员函数onDraw。

status_t DisplayListRenderer::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) {
    bitmap = refBitmap(bitmap);
    paint = refPaint(paint);
    addDrawOp(new (alloc()) DrawBitmapOp(bitmap, paint));
    return DrawGlInfo::kStatusDone;
}

    DisplayList构建完成后通过调用RenderNode.end()将DisplayListRenderer::mDisplayListData中的数据同步到RenderNode::mStagingDisplayListData中,同时mNeedsDisplayListDataSync也会被置为true;

    RenderNode::mStagingProperties由Java层RenderNode进行更新;

    RenderNode::mDirtyPropertyFields由Java层RenderNode进行更新;

    SurfaceTexture更新后便会将对应的Layer加入 DrawFrameTask::mLayers;

你可能感兴趣的:(android系统层)