上一篇讲了Activity的绘制流程(三)VSync(https://www.jianshu.com/p/0d9ade638bc8),本篇主讲(四)Surface。
(一)窗口的添加
(二)Choreographer
(三)VSync
(四)Surface
(五)RenderThread
(六)StartingWIndow
(七)窗口切换
一、绘制流程
从前面的介绍中,我们得知了一个Activity对应一个Window,一个Window包含多个View,一个Window又对应一个Surface,Surface相当与一块画布,管理着图形缓冲区。
使用硬件渲染后,主线程的绘制工作主要是采用递归的方式遍历DecorView包含的所有的View,构建和更新一个DisplayList。DisplayList主要包含两个重要的对象,一个是deque队列mChildNodes,View对应一个RenderNode,RenderNode被封装在一个RenderNodeDrawable对象中,并加入到该队列,该队列用于后续同步时取得保存的子RenderNode。可以将这个队列想象成一个树结构来理解,每个节点为RenderNodeDrawable。另一个是DisplayListData对象mDisplayList,该对象中保存了各个Op,一个Op代表一个绘制操作,比如绘制多个点 DrawPoints,比如绘制子View DrawDrawable。
接着主线程会进入睡眠,直到RenderThread线程完成DisplayList的同步工作。然后RenderThread线程会从Surface对应的图形缓冲区队列BufferQueue中申请一块图形缓冲区GraphicBuffer(当然这里的GraphicBuffer只是将物理内存映射到应用的进程空间)进行绘制,简单来说就是将DisplayList中的各个绘制操作填充进去,最后将该GraphicBuffer放回队列中,至此完成了CPU上的绘制流程。
二、Surface
在Activity的绘制流程(一)窗口的添加中提到了ViewRootImpl的setView函数,在调用Session的addToDisplay函数添加窗口之前,调用了ViewRootImpl的requestLayout函数,该函数往Choreographer的CallbackQueue CALLBACK_TRAVERSAL中添加一个Callback,并且请求VSync。当接收到VSync后,主线程处理CallbackQueue数组中的各个回调,其中CALLBACK_TRAVERSAL会向WMS请求重新布局该窗口,WMS将返回一个SurfaceControl对象给应用,应用端将SurfaceControl对象保存到Surface(https://www.androidos.net.cn/android/10.0.0_r6/xref/frameworks/base/core/java/android/view/Surface.java)中,后续通过该Surface便可以调度到SurfaceFlinger端操作对应的BufferQueue中的GraphicBuffer。
ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
requestLayout();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
mTempInsets);
...
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
...
}
}
创建Surface,其实是在SurfaceFlinger端为Window创建了一个BufferQueue。BufferQueueCore是BufferQueue的实现类,BufferQueueCore初始化的时候并不是triple buffers,而是double buffers。
应用端通过BufferQueueProducer来allocateBuffers(分配GraphicBuffer)、dequeueBuffer(申请GraphicBuffer,映射到应用的进程空间)、queueBuffer(将GraphicBuffer放回BufferQueue)
SurfaceFlinger通过BufferQueueConsumer来acquireBuffer(申请GraphicBuffer)、releaseBuffer(释放GraphicBuffer)
所以BufferQueue、应用端、SurfaceFlinger构成了一个生产者-消费者模型,由应用端绘制Buffer,绘好的Buffer被SurfaceFlinger用于合成。
BufferQueueCore.cpp
mMaxAcquiredBufferCount(1),
mMaxDequeuedBufferCount(1),
在调用BufferQueueLayer的onFirstRef函数时,会去判断系统属性ro.sf.disable_triple_buffer的值,默认为0,即设置为triple buffers,可以通过修改系统属性ro.sf.disable_triple_buffer修改为double buffers。
BufferQueueLayer.cpp
void BufferQueueLayer::onFirstRef() {
...
if (!mFlinger->isLayerTripleBufferingDisabled()) {
mProducer->setMaxDequeuedBufferCount(2);
}
...
}
在Android Q中,若帧率大于66.6,即日常见到的90帧、120帧,会将triple buffers改为four buffers。
CanvasContext.cpp
void CanvasContext::setSurface(sp&& surface) {
...
if (mRenderAheadDepth == 0 && DeviceInfo::get()->getMaxRefreshRate() > 66.6f) {
mFixedRenderAhead = false;
mRenderAheadCapacity = 1;
} else {
mFixedRenderAhead = true;
mRenderAheadCapacity = mRenderAheadDepth;
}
bool hasSurface = mRenderPipeline->setSurface(mNativeSurface.get(), mSwapBehavior, colorMode,mRenderAheadCapacity);
...
}
SkiaOpenGLPipeline.cpp
bool SkiaOpenGLPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior,
ColorMode colorMode, uint32_t extraBuffers) {
...
setBufferCount(surface, extraBuffers);
...
}
static void setBufferCount(ANativeWindow* window, uint32_t extraBuffers) {
int query_value;
int err = window->query(window, NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &query_value);
auto min_undequeued_buffers = static_cast(query_value);
int bufferCount = min_undequeued_buffers + 2 + extraBuffers;
native_window_set_buffer_count(window, bufferCount);
}
Surface.cpp
int Surface::setBufferCount(int bufferCount) {
...
err = mGraphicBufferProducer->query(
NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &minUndequeuedBuffers);
if (err == NO_ERROR) {
err = mGraphicBufferProducer->setMaxDequeuedBufferCount(
bufferCount - minUndequeuedBuffers);
}
...
}
三、allocateBuffers
GraphicBufferAllocator是Android为上层提供的一个类,用于分配图形缓冲区。图形缓冲区可以从帧缓冲区,也可以从内存中分配,图形缓冲区从帧缓冲区中分配值用于SurfaceFlinger。应用进程申请分配图形缓冲区,由SurfaceFlinger从内存中分配,分配的时候会将物理内存映射到SurfaceFlinger的进程空间,后续当应用端通过BufferQueueProducer来dequeueBuffer函数时才把图形缓冲区映射到应用进程的进程空间。
在BufferQueueCore中有五个重要的列表:
set
list
list
set
BufferSlot[] mSlots(保存了所有GraphicBuffer)
在BufferQueueProducer的allocateBuffers函数中,创建一个GraphicBuffer对象。在这里只会创建一个GraphicBuffer,而不是所有Buffer。然后从mFreeSlots取出一个值,mFreeBuffers放入一个值,代表创建了一个Buffer,并将Buffer保存到列表mSlots中。
四、dequeueBuffer
从systrace中可以看到,dequeueBuffer和queueBuffer都在RenderThread线程上。RenderThread线程申请GraphicBuffer是调用了Surface(非上面提及到的Surface,而是https://www.androidos.net.cn/android/10.0.0_r6/xref/frameworks/native/libs/gui/Surface.cpp)的dequeueBuffer函数,通过Binder通信调用到SurfaceFlinger端申请GraphicBuffer。在allocateBuffers中说了创建了一个GraphicBuffer,所以若是在列表mFreeBuffers中申请不到GraphicBuffer且已经申请的GraphicBuffer数目没有超过阈值,则创建一个GraphicBuffer对象。得到GraphicBuffer后,将其映射到应用进程的进程空间。
五、queueBuffer
将DisplayList中的各个绘制操作填充到GraphicBuffer,RenderThread线程通过调用Surface的queueBuffer函数binder通信SurfaceFlinger端,将该GraphicBuffer放回到对应的队列中,至此完成了CPU上的绘制流程。但是并不代表这一帧已经绘制完成,该帧的绘制需要GPU的参与,而CPU不等带GPU绘制完成,是为了让CPU和GPU能够并行操作,减少耗时。
六、Fence
CPU和GPU并行操作GraphicBuffer,必然会牵扯到同步的问题,即CPU如何得知GPU已经完成一帧的绘制,从而引入了Fence。
在queueBuffer时,会给GraphicBuffer添加一个Fence,当SurfaceFlinger端调用BufferQueueConsumer的acquireBuffer函数获取GraphicBuffer用于合成时,若GPU还没有执行到这条Fence指令,则会等待,直到GPU执行到Fence指令,才会从等待中被唤醒。
Fence有sync_pt、sync_timeline和sync_fence三个概念:
- sync_timeline:时间轴。每个流程必须有一个时间轴,时间轴是一个单调递增的计数器,时间轴由时间值组成,时间轴上可以指定有许多同步点,当时间轴当前递增超过该同步点,则同步点状态切换。
- sync_pt:同步点,代表时间轴上一个特殊的值,有三种状态:active(刚创建,此时一般时间轴都是小于pt)、signal(时间轴增大到同步点)、error。
- sync_fence:一系列同步点的集合,每个同步点只能属于一个Fence,一个Fence包含的同步点可以来自不同的时间轴,Fence可以合并,当Fence中的所有同步点被唤醒后,Fence状态切换。