前面介绍了关于绘制相关的一些组件,如RecordingCanvas,RenderThread,RenderProxy,CanvasContext,DisplayList,HardwareRender等等,这些准备工作搭建了好之后,才可以开启真正的绘制。因此我们来继续分析一帧的绘制的流程,它是由DrawFrameTask来完成的,我们还是从Java层的ViewRootImpl讲起
java
private void performDraw() {
...
boolean canUseAsync = draw(fullRedrawNeeded);
...
}
java
private boolean draw(boolean fullRedrawNeeded) {
...
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
...
}
从而进入到ThreadedRenderer.draw方法
frameworks/base/core/java/android/view/ThreadedRenderer.java
java
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
...
updateRootDisplayList(view, callbacks);
...
int syncResult = syncAndDrawFrame(frameInfo);
...
}
在draw方法里主要作了两件事件
- updateRootDisplayList 更新UI树的DisplayList,记录Canvas的绘制操作Op,这里指的是记录到stagingDisplayList的DisplauListData
- syncAndDrawFrame 同步UI更新到displayList,然后绘制帧,将Op绘制到SkCanvas
updateRootDisplayList这个方法之前已经详细介绍过,这就不作过多的介绍,这里需要注意是,它绘制的是RootRenderNode,它对应的View也是ViewRootImpl所持有的根 View,因此是整个树绘画的起点。这个根View的updateDisplayListIfDirty())方法将返回包含整个树更新后的RenderNode,然后直接使用drawRenderNode方法将其记录到RootNode的stagingDisplayList中去. 这个过程将在C层构建出一颗RenderNode树。
java
private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
RecordingCanvas canvas = mRootNode.beginRecording(mSurfaceWidth, mSurfaceHeight);
...
canvas.drawRenderNode(view.updateDisplayListIfDirty());
...
} finally {
mRootNode.endRecording();
}
}
}
1 syncAndDrawFrame
我们这篇文章主要分析一下syncAndDrawFrame这个方法,这里的同步指的是stagingDisplayList数据到displayList的同步,同步完之后执行一帧的绘制。
frameworks/base/graphics/java/android/graphics/HardwareRenderer.java
java
public int syncAndDrawFrame(@NonNull FrameInfo frameInfo) {
return nSyncAndDrawFrame(mNativeProxy, frameInfo.frameInfo, frameInfo.frameInfo.length);
}
frameworks/base/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
java
{"nSyncAndDrawFrame", "(J[JI)I", (void*)android_view_ThreadedRenderer_syncAndDrawFrame}
static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz, jlong proxyPtr, jlongArray frameInfo, jint frameInfoSize) {
...
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
env->GetLongArrayRegion(frameInfo, 0, frameInfoSize, proxy->frameInfo());
return proxy->syncAndDrawFrame();
}
Java层会传入已经构建好的RenderProxy指针,从转换成C层的RenderProxy对象,然后调用syncAndDrawFrame方法
frameworks/base/libs/hwui/renderthread/RenderProxy.cpp
java
int RenderProxy::syncAndDrawFrame() {
return mDrawFrameTask.drawFrame();
}
这里就进入到本文的主角DrawFrameTask,mDrawFrameTask是在RenderProxy的成员,初始化的时候同步初始化的。 proxy调用mDrawFrameTask.drawFrame来绘制一帧
frameworks/base/libs/hwui/renderthread/DrawFrameTask.cpp
java
int DrawFrameTask::drawFrame() {
LOG_ALWAYS_FATAL_IF(!mContext, "Cannot drawFrame with no CanvasContext!");
mSyncResult = SyncResult::OK;
mSyncQueued = systemTime(SYSTEM_TIME_MONOTONIC);
postAndWait();
return mSyncResult;
}
void DrawFrameTask::postAndWait() {
AutoMutex _lock(mLock);
mRenderThread->queue().post([this]() { run(); });
mSignal.wait(mLock);
}
这里是王RenderThread的任务队列post一个lamda来执行DrawFrameTask的run方法
java
void DrawFrameTask::run() {
TreeInfo info(TreeInfo::MODE_FULL, *mContext);
canUnblockUiThread = syncFrameState(info);
canDrawThisFrame = info.out.canDrawThisFrame;
....
nsecs_t dequeueBufferDuration = 0;
if (CC_LIKELY(canDrawThisFrame)) {
dequeueBufferDuration = context->draw();
} else {
...
}
....
}
这里只分析主流程,else部分先省略。这里主要有两个方法
- syncFrameState 同步帧的状态,这是主要的工作
- 调用context->draw 绘制内容
本文将分析一下syncFrameState的原理,context->draw的内容在下一篇中继续分析
2 syncFrameState
java
bool DrawFrameTask::syncFrameState(TreeInfo& info) {
...
bool canDraw = mContext->makeCurrent();
...
mContext->prepareTree(info, mFrameInfo, mSyncQueued, mTargetNode);
...
}
下面主要分析一下这个两个流程
- mContext->makeCurrent()
- mContext->prepareTree
2.1 mContext.makeCurrent
mContext是CanvasContext对象,它持有绘制需要的所有依赖。syncFrameState 首先通过调用mContext->makeCurrent()来通知eglManager将mContext 的 EGLSurface对象设置成当前对象,这样在后续EGL绘制就是针对这个当前的EGLSurface。仅仅分析一下OpenGL的情况
frameworks/base/libs/hwui/renderthread/CanvasContext.cpp
java
bool CanvasContext::makeCurrent() {
if (mStopped) return false;
auto result = mRenderPipeline->makeCurrent();
switch (result) {
case MakeCurrentResult::AlreadyCurrent:
return true;
case MakeCurrentResult::Failed:
mHaveNewSurface = true;
setSurface(nullptr);
return false;
case MakeCurrentResult::Succeeded:
mHaveNewSurface = true;
return true;
default:
LOG_ALWAYS_FATAL("unexpected result %d from IRenderPipeline::makeCurrent",
(int32_t)result);
}
return true;
}
frameworks/base/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
java
MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() {
// TODO: Figure out why this workaround is needed, see b/13913604
// In the meantime this matches the behavior of GLRenderer, so it is not a regression
EGLint error = 0;
if (!mEglManager.makeCurrent(mEglSurface, &error)) {
return MakeCurrentResult::AlreadyCurrent;
}
return error ? MakeCurrentResult::Failed : MakeCurrentResult::Succeeded;
}
这里通过mEglManager来切换mEglSurface成当前的EGLSurface。 这个mEglSurface是在HardwareRender初始化的时候创建的
2.2 mContext.prepareTree
java
void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued,
RenderNode* target) {
...
for (const sp<RenderNode>& node : mRenderNodes) {
...
info.mode = (node.get() == target ? TreeInfo::MODE_FULL : TreeInfo::MODE_RT_ONLY);
node->prepareTree(info);
GL_CHECKPOINT(MODERATE);
}
...
info.out.canDrawThisFrame = true;
if (info.out.canDrawThisFrame) {
int err = mNativeSurface->reserveNext();
...
}
...
}
大部分情况下,mRenderNodes中只有一个元素,就是在初始化时传入的RootRenderNode,这里target的是RootRenderNode,所以info.model 的值将会设置成TreeInfo::MODE_FULL 。从而进入到RootRenderNode的prepareTree方法。在遍历了整个树做好同步之后,
在调用mNativeSurface的reserveNext预定一个GraphicBuffer。
frameworks/base/libs/hwui/RootRenderNode.cpp
java
void RootRenderNode::prepareTree(TreeInfo& info) {
...
RenderNode::prepareTree(info);
...
}
frameworks/base/libs/hwui/RenderNode.cpp
java
void RenderNode::prepareTree(TreeInfo& info) {
...
prepareTreeImpl(observer, info, false);
...
}
java
void RenderNode::prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer) {
if (info.mode == TreeInfo::MODE_FULL) {
pushStagingPropertiesChanges(info);
}
if (info.mode == TreeInfo::MODE_FULL) {
pushStagingDisplayListChanges(observer, info);
}
if (mDisplayList) {
info.out.hasFunctors |= mDisplayList.hasFunctor();
mHasHolePunches = mDisplayList.hasHolePunches();
bool isDirty = mDisplayList.prepareListAndChildren(
observer, info, childFunctorsNeedLayer,
[this](RenderNode* child, TreeObserver& observer, TreeInfo& info,
bool functorsNeedLayer) {
child->prepareTreeImpl(observer, info, functorsNeedLayer);
mHasHolePunches |= child->hasHolePunches();
});
...
}
pushLayerUpdate(info);
这里主要又有4个逻辑。
- 同步properties
- 同步displayList
- 遍历RenderNode树,对子节点递归调用prepareTreeImpl
- 如果该RenderNode是的type是RenderLayer的话,为其准备一个用于绘制的SkSurface
2.2.1 同步properties
java
void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) {
if (mDirtyPropertyFields) {
mDirtyPropertyFields = 0;
...
syncProperties();
...
}
}
接着调用syncProperties,这个方法之前介绍过
java
void RenderNode::syncProperties() {
mProperties = mStagingProperties;
}
2.2.2 同步displayList
java
void RenderNode::pushStagingDisplayListChanges(TreeObserver& observer, TreeInfo& info) {
if (mNeedsDisplayListSync) {
mNeedsDisplayListSync = false;
...
syncDisplayList(observer, &info);
...
}
}
接着调用syncDisplayList
java
void RenderNode::syncDisplayList(TreeObserver& observer, TreeInfo* info) {
...
if (mStagingDisplayList) {
mStagingDisplayList.updateChildren([](RenderNode* child) { child->incParentRefCount(); });
}
deleteDisplayList(observer, info);
mDisplayList = std::move(mStagingDisplayList);
...
}
先调用deleteDisplayList把之前的内容清理掉,然后在将mStagingDisplayList的移动到mDisplayList,从而完成同步
java
void RenderNode::deleteDisplayList(TreeObserver& observer, TreeInfo* info) {
if (mDisplayList) {
mDisplayList.updateChildren(
[&observer, info](RenderNode* child) { child->decParentRefCount(observer, info); });
mDisplayList.clear(this);
}
}
2.2.3 遍历子树
java
bool SkiaDisplayList::prepareListAndChildren(
TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer,
std::function<void(RenderNode*, TreeObserver&, TreeInfo&, bool)> childFn) {
...
for (auto& child : mChildNodes) {
RenderNode* childNode = child.getRenderNode();
...
childFn(childNode, observer, info, functorsNeedLayer);
...
}
...
}
遍历mChildNodes,它是一个RenderNodeDrawable的dequeue, 之前分析DisplayList的时候介绍个类,它包装了一个RenderNode,针对每个RenderNodeDrawable持有的RenderNode,调用childFn来处理。这是外部传来的回调函数
java
[this](RenderNode* child, TreeObserver& observer, TreeInfo& info,
bool functorsNeedLayer) {
child->prepareTreeImpl(observer, info, functorsNeedLayer);
于是递归调用child->prepareTreeImpl(observer, info, functorsNeedLayer);方法,对整棵树进行遍历。
2.2.4 pushLayerUpdate
如果当前RenderNode的type是RenderLayer 且有内容可以绘制,会为当前的RenderNode生成一个使用GPU绘制的SkSurface,并保存到RenderNode的setLayerSurface,之后将RenderNode和它对应的damage保存到TreeInfo的layerUpdateQueue。如果damage为空的话,表示没有变化的区域。这些数将将在后面的绘制用使用。 Layer的作用是将一些绘制指令固定下来到一个纹理,这样可以减少同一段指令的执行次数。在绘制这个RenderNode的是时候,直接绘制这个纹理。这适合那些不经常变化的节点,但是大部分默认的View或者RenderNode的type并不是RenderLayer,而是LAYER_TYPE_NONE。当我们确信某个View的属性不会发生变化的时候,可以通过调用View.setLayerType(LAYER_TYPE_HARDWARE,paint)的方式让底层为其准备一个LayerSurface。这正是pushLayerUpdate对应的逻辑
java
void RenderNode::pushLayerUpdate(TreeInfo& info) {
LayerType layerType = properties().effectiveLayerType();
if (CC_LIKELY(layerType != LayerType::RenderLayer) || CC_UNLIKELY(!isRenderable()) ||
CC_UNLIKELY(properties().getWidth() == 0) || CC_UNLIKELY(properties().getHeight() == 0) ||
CC_UNLIKELY(!properties().fitsOnLayer())) {
if (CC_UNLIKELY(hasLayer())) {
this->setLayerSurface(nullptr);
}
return;
}
if (info.canvasContext.createOrUpdateLayer(this, *info.damageAccumulator, info.errorHandler)) {
damageSelf(info);
}
if (!hasLayer()) {
return;
}
SkRect dirty;
info.damageAccumulator->peekAtDirty(&dirty);
info.layerUpdateQueue->enqueueLayerWithDamage(this, dirty);
if (!dirty.isEmpty()) {
mStretchMask.markDirty();
}
// There might be prefetched layers that need to be accounted for.
// That might be us, so tell CanvasContext that this layer is in the
// tree and should not be destroyed.
info.canvasContext.markLayerInUse(this);
}
frameworks/base/libs/hwui/renderthread/CanvasContext.h
arduino
bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& dmgAccumulator,
ErrorHandler* errorHandler) {
return mRenderPipeline->createOrUpdateLayer(node, dmgAccumulator, errorHandler);
}
frameworks/base/libs/hwui/pipeline/skia/SkiaPipeline.cpp
java
bool SkiaPipeline::createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator,
ErrorHandler* errorHandler) {
// compute the size of the surface (i.e. texture) to be allocated for this layer
const int surfaceWidth = ceilf(node->getWidth() / float(LAYER_SIZE)) * LAYER_SIZE;
const int surfaceHeight = ceilf(node->getHeight() / float(LAYER_SIZE)) * LAYER_SIZE;
SkSurface* layer = node->getLayerSurface();
if (!layer || layer->width() != surfaceWidth || layer->height() != surfaceHeight) {
SkImageInfo info;
info = SkImageInfo::Make(surfaceWidth, surfaceHeight, getSurfaceColorType(),
kPremul_SkAlphaType, getSurfaceColorSpace());
SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
SkASSERT(mRenderThread.getGrContext() != nullptr);
node->setLayerSurface(SkSurface::MakeRenderTarget(mRenderThread.getGrContext(),
SkBudgeted::kYes, info, 0,
this->getSurfaceOrigin(), &props));
...
return true;
}
return false;
}
根据node的尺寸生成一个SkImageInfo,然后调用MakeRenderTarget再生成一个SkSurface,MakeRenderTarget方法将返回一个在GPU完成绘制的surface。然后将该surface作为参数,调用node->setLayerSurface方法设置到RenderNode中去。最后在调用enqueueLayerWithDamage将RenderNode保存到LayerUpdateQueue的mEntries中( 在绘制的时候会先绘制有更新的Layer)
frameworks/base/libs/hwui/LayerUpdateQueue.cpp
java
void LayerUpdateQueue::enqueueLayerWithDamage(RenderNode* renderNode, Rect damage) {
damage.roundOut();
damage.doIntersect(0, 0, renderNode->getWidth(), renderNode->getHeight());
if (!damage.isEmpty()) {
for (Entry& entry : mEntries) {
if (CC_UNLIKELY(entry.renderNode == renderNode)) {
entry.damage.unionWith(damage);
return;
}
}
mEntries.emplace_back(renderNode, damage);
}
}
damage为空会并被忽略。
2.3 mNativeSurface->reserveNext
在遍历完RendNode树之后,会调用mNativeSurface去申请GraphicBuffer,mNativeSurface的类型是ReliableSurface,它是对ANativeWindow的封装,即对Surface的封装,所以最后还是通过Surface去申请的
java
int ReliableSurface::reserveNext() {
int fenceFd = -1;
ANativeWindowBuffer* buffer = nullptr;
// Note that this calls back into our own hooked method.
int result = ANativeWindow_dequeueBuffer(mWindow, &buffer, &fenceFd);
{
std::lock_guard _lock{mMutex};
LOG_ALWAYS_FATAL_IF(mReservedBuffer, "race condition in reserveNext");
mReservedBuffer = buffer;
mReservedFenceFd.reset(fenceFd);
}
return result;
}
这里ANativeWindowBuffer是一个结构体,它是GraphicBuffer的父类。
frameworks/native/libs/nativewindow/include/android/hardware_buffer.h
java
typedef struct AHardwareBuffer AHardwareBuffer;
frameworks/native/libs/ui/include/ui/GraphicBuffer.h
java
class GraphicBuffer
: public ANativeObjectBase<ANativeWindowBuffer, GraphicBuffer, RefBase>,
public Flattenable<GraphicBuffer>
{
friend class Flattenable<GraphicBuffer>;
通过这个全局函数ANativeWindow_dequeueBuffer,会调用到window的dequeueBuffer方法,经过Surface拦截后,调用到Surface的dequeBuffer,然后Surface再调用它的producer去dequeueBuffer,完成buffer的申请。因此reserveNext 是真正触发申请graphicbuffer的地方
3 总结
这一篇文章我们开始进入到绘制帧的流程分析,从Java的draw方法开始,进行一帧的和绘制,会通过C层的RenderProxy代理后,转发到RenderContext的draw方法,在转发到DrawFrameTask.drawFrame方法,它会通过王RenderThread的queue创建一个任务,于是进入到RenderThread这个子线程里去执行绘制逻辑。 绘制时:
- 首先将调用EGLManager.makeCurrent()将当前的EGLSurface作为OpenGL的绘制表面,
- 然后同步RenderNode树形结构的属性和displayList,并为type为RENDER_LAYER的RenderNode创建使用GPU绘制的SkSurface。
- 最后调用mNativeSurface.reserveNext 准备GraphicBuffer。
到这里,绘制需要的资源,需要绘制的内容都准备就绪,接下来就是开始执行绘制动作context->draw(),这将在下一篇中继续分析
👀关注公众号:Android老皮!!!欢迎大家来找我探讨交流👀