文章记录自己对RenderThread学习过程,供日后回顾。
我们知道5.0上为每个进程新增了一个RenderThread线程,这是一个附加的UI线程?我们通过学习这块代码来熟悉它。
第一部分:RenderThread线程的启动
RenderThread线程的启动过程如下图所示。
涉及相关的类关系如下图:
有两个注意点:①每一个窗口对应一个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()等工作。调用逻辑如下图:
接收到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到队列中,这个是用来做动画的
}
}
}
上面涉及相关的类关系如下:
从前面那个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的过程
大体流程如下:
借用老罗的总结:
从这里就可以看到,将一个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过程
渲染过程大致如下:
第四部分: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;