前言
为什么要做这么一篇分享?
首先是以前对 Android渲染都是浅尝辄止,感觉懂了,又感觉什么都不懂,很多东西看了又忘,主要原因还是这里的知识点太散乱,没有形成一个知识网。所以准备系统的梳理下图形显示系统,巩固下这块知识,并梳理成文档,方便给日后给自己回顾。
其次是看看应用层有没有哪些地方可以配合优化,顺便学习下这里的设计思想,看能不能应用到业务场景。
最后能给其他同学带来点启发,那就更好了。
本篇会讲 App内渲染全链路上的细节,从一个View的重绘一直到调用 SurfaceFlinger 上屏,SurfaceFlinger上屏后的细节后续会单开一篇文章。这里只讲硬件加速并使用了 RenderThread 后的渲染流程,本次会简单介绍。RenderThread 是个宝藏设计,可挖掘的东西比较多,应用层巧妙使用后,会大幅提高应用流畅度。后续也会单独拎出来讲一讲。软件渲染的流程相对比较简单,就不详细分析了。
(本篇文章基于Android12.0源码分析,有些内容可能已经过时)
渲染总流程
图形显示系统
在开始介绍显示系统之前,先列几个重要的类,单独看可能会很难理解,可以对着下面的图一起看。
专业名词 | 解释 |
---|---|
canvas | 翻译过来就是画布,上层的绘制命令都是调用该方法去完成的。比如canvas.drawLine(),drawPoint()等。根据不同阶段的调用分为,RecordingCanvas、SkiaCanvas、SkCanvas。 |
surface | 图层,它可以控制屏幕缓冲区,一个surface对应一个window,也对应一个SurfaceFlinger 中的Layer。native 层的surface继承自ANativeWindow,通过dequeueBuffer和queueBuffer 来进行缓冲区数据的交换。 |
SurfaceFlinger | 系统的 独立service,主要干以下几件事:1 接受其他app 的图形更新请求2 将所有app的界面合成一张图像3 将最终的图像数据传给显示器,渲染到屏幕上 |
BufferQueue | 帧缓存,缓存大小一般是2或者3个,对应黄油计划的双缓冲与三缓冲。Android12之前在SurfaceFlinger中有layer创建,12及其之后放到了app内部创建,并改名为BLASTBufferQueue。 |
GraphicBuffer | 一个共享缓冲区,可以跨进程、跨硬件调用。 |
layer | SurfaceFlinger 里面的 surface,与应用层的 surface 一一对应。Android12之前,共享缓存也是由 layer 去创建的。 |
HWComposer | 硬件组合抽象层,由厂商实现,不同的厂商实现是不一样的,主要就是为了 SurfaceFlinger 提供硬件支持(非GPU)。 |
RenderThread | Android5.0 为了降低主线程的压力,将一些在UI线程中执行的动作移到了一个子线程,这个子线程就是RenderThread,从而实现了异步更新UI的能力。 |
应用层通过 canvas 将 view树 渲染到缓存中,然后通过 **surface **将自己的渲染数据放入 Buffer Queue 的共享内存,最终通过 binder 通知 surfaceflinger 进行合成,接下来会详细介绍 App内的渲染细节。
普通view的渲染流程
Vsync 跟编舞者的更新流程可以参考我的另外一篇文章 juejin.cn/post/741577…
本篇文章会重点分析 ThreadRender.draw 之后的操作。
View树的更新与渲染
在看这块代码的时候,一直有一个点困惑着我,树状结构到底在哪,源码翻了好几遍才找到。在下面我会详细介绍。
所以在详细分析这里流程之前,先简单介绍下这里的树状结构。
树状结构介绍
在 java 层看不到树状结构,树状结构在native 层,并且树状结构是通过RenderNode 、DisplayList 、RenderNodeDrawable 这三个类组成。
树状结构
(下面都是native层的类)
-
RootRenderNode
相关类图
相关类的解释:
类名 | 说明 |
---|---|
RenderNode | 渲染节点,保存了view相关的属性,持有 SkiaDisplayList。Java层的RendeNode 会持有 SkiaRecordingCanvas 对象。Native层的不持有。 |
RootRenderNode | 根节点对应的RenderNode,RootRenderNode,继承自RenderNode。在HardwareRenderer 初始化的时候创建。 |
SkiaRecordingCanvas | 硬件渲染的画布,在硬件加速逻辑中的canvas都是它,持有RenderNode 的 SkiaDisplayList ,RenderNode添加与移除子节点的操作都是由SkiaRecordingCanvas来完成,同时所有canvas.drawxxx()方法都会转换成 DisplayListData,并放置到SkiaDisplayList中。是构建View树背后的英雄。 |
RecordingCanvas | Java层跟Native层的 RecordingCanvas 毫无关系(系统源码一言难尽)Java 层的 RecordingCanvas 对应 SkiaRecordingCanvas,java层的canvas都是指它。Native层的 RecordingCanvas 是用来调用 Skia层的(这是一个2D图形库,真正的绘制逻辑在这一层),RecordingCanvas 相当于 Skia 库跟 Android系统的适配层。 |
SkiaDisplayList | 持有DisplayListData,包含了当前节点的绘制命令,以及持有子节点 deque<RenderNodeDrawable>。 |
DisplayListData | 当前节点的绘制命令 |
RenderProperties | 保存了当前节点的UI属性,比如渲染位置,宽高,透明度,缩放值等等。 |
RenderNodeDrawable | 封装了子节点的RenderNode,只有ViewGroup有子节点的时候才具有 |
更新树状结构DisplayList
这里的主要逻辑都是在 Native 层,Java层最重要的方法就是 view.updateDisplayListIfDirty 、 view.draw 、ViewGroup.dispatchDraw通过这三个方法递归创建最终需要渲染的指令树。
根节点的更新跟子节点有一些区别,根节点的更新在ThreadedRenderer类里面,普通节点的更新在View里面。
添加根节点
(java 层调用树)
// frameworks/base/core/java/android/view/ViewRootImpl.java
private boolean draw(boolean fullRedrawNeeded) {
...
if (isHardwareEnabled()) {
// 开启了硬件加速
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
} else {
// 走软件渲染
drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)
}
...
}
// android/view/ThreadedRenderer.java
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
...
// 更新view树节点list,递归构建DisplayList
updateRootDisplayList(view, callbacks);
...
// 交给RenderThread去渲染
int syncResult = syncAndDrawFrame(frameInfo);
...
}
private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
...
updateViewTreeDisplayList(view);
RecordingCanvas canvas = mRootNode.beginRecording(mSurfaceWidth, mSurfaceHeight);
...
canvas.drawRenderNode(view.updateDisplayListIfDirty());
...
}
// android/view/View.java
public RenderNode updateDisplayListIfDirty() {
final RenderNode renderNode = mRenderNode;
...
if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
|| !renderNode.hasDisplayList()
|| (mRecreateDisplayList)) {
...
if (renderNode.hasDisplayList()
&& !mRecreateDisplayList) {
...
// 遍历整个view树
dispatchGetDisplayList();
...
return renderNode; // no work needed
}
}
// 硬件渲染这里会取一个自身的Canvas,这是一个特殊的canvas
final RecordingCanvas canvas = renderNode.beginRecording(width, height);
...
draw(canvas);
...
}
return renderNode;
}
将节点添加进去的代码在 Native层 。
先是根据java层的对象获取到对应的 Native 层 RenderNode,再是将 RenderNode 绘制到 Canvas上
// frameworks/base/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
static void android_view_DisplayListCanvas_drawRenderNode(CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr, jlong renderNodePtr) {
Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr);
RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
canvas->drawRenderNode(renderNode);
}
// frameworks/base/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
void SkiaRecordingCanvas::drawRenderNode(uirenderer::RenderNode* renderNode) {
// Record the child node. Drawable dtor will be invoked when mChildNodes deque is cleared.
mDisplayList->mChildNodes.emplace_back(renderNode, asSkCanvas(), true, mCurrentBarrier);
...
drawDrawable(&renderNodeDrawable);
...
}
void drawDrawable(SkDrawable* drawable) { mCanvas->drawDrawable(drawable); }
添加普通节点
添加普通节点的方法在 View中,Java层的代码如下,Native层的代码同根节点一致。
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
...
if (drawingWithRenderNode) {
...
((RecordingCanvas) canvas).drawRenderNode(renderNode);
...
}
...
}
添加绘制指令
所有的绘制指令都是通过 RecordingCanvas 来完成的。下面我们就来详细介绍下这个类。
Java 层叫 RecordingCanvas(Java),Natve 层对应的类叫SkiaRecordingCanvas,持有native层 RecordingCanvas(Native) 对象。
一个普通view的draw流程
这里我们通过一个view的drawLine()介绍,假设我们有一个自定义view就只画一条线。
Canvas.drawLine->BaseCanvas.drawLine->BaseCanvas.nDrawLine->SkiaCanvas::drawLine()->SkCanvas::drawLine->SkCanvas::drawPoints->RecordingCanvas::onDrawPoints->DisplayListData::drawPoints->DisplayListData::push(DrawPoints)
drawLine跟drawRect有一点小区别,drawLine会在SkCanvas中转换成drawPoints,最终还是会回调到RecordingCanvas统一处理,详细源码如下:
// frameworks/base/graphics/java/android/graphics/Canvas.java
public void drawLine(float startX, float startY, float stopX, float stopY,
@NonNull Paint paint) {
super.drawLine(startX, startY, stopX, stopY, paint);
}
// frameworks/base/graphics/java/android/graphics/BaseCanvas.java
public void drawLine(float startX, float startY, float stopX, float stopY,
@NonNull Paint paint) {
throwIfHasHwBitmapInSwMode(paint);
nDrawLine(mNativeCanvasWrapper, startX, startY, stopX, stopY, paint.getNativeInstance());
}
private static native void nDrawLine(long nativeCanvas, float startX, float startY, float stopX,
float stopY, long nativePaint);
// frameworks/base/libs/hwui/SkiaCanvas.cpp
void SkiaCanvas::drawLine(float startX, float startY, float stopX, float stopY,
const Paint& paint) {
applyLooper(&paint,
[&](const SkPaint& p) { mCanvas->drawLine(startX, startY, stopX, stopY, p); });
}
// external/skia/src/core/SkCanvas.cpp
void SkCanvas::drawLine(SkScalar x0, SkScalar y0, SkScalar x1, SkScalar y1, const SkPaint& paint) {
SkPoint pts[2];
pts[0].set(x0, y0);
pts[1].set(x1, y1);
this->drawPoints(kLines_PointMode, 2, pts, paint);
}
void SkCanvas::drawPoints(PointMode mode, size_t count, const SkPoint pts[], const SkPaint& paint) {
TRACE_EVENT0("skia", TRACE_FUNC);
// 这里的this是RecordingCanvas对象。
this->onDrawPoints(mode, count, pts, paint);
}
// frameworks/base/libs/hwui/RecordingCanvas.cpp
void RecordingCanvas::onDrawPoints(SkCanvas::PointMode mode, size_t count, const SkPoint pts[],
const SkPaint& paint) {
fDL->drawPoints(mode, count, pts, paint);
}
void DisplayListData::drawPoints(SkCanvas::PointMode mode, size_t count, const SkPoint points[],
const SkPaint& paint) {
void* pod = this->push<DrawPoints>(count * sizeof(SkPoint), mode, count, paint);
copy_v(pod, points, count);
}
template <typename T, typename... Args>
void* DisplayListData::push(size_t pod, Args&&... args) {
size_t skip = SkAlignPtr(sizeof(T) + pod);
SkASSERT(skip < (1 << 24));
if (fUsed + skip > fReserved) {
static_assert(SkIsPow2(SKLITEDL_PAGE), "This math needs updating for non-pow2.");
// Next greater multiple of SKLITEDL_PAGE.
fReserved = (fUsed + skip + SKLITEDL_PAGE) & ~(SKLITEDL_PAGE - 1);
fBytes.realloc(fReserved);
LOG_ALWAYS_FATAL_IF(fBytes.get() == nullptr, "realloc(%zd) failed", fReserved);
}
SkASSERT(fUsed + skip <= fReserved);
auto op = (T*)(fBytes.get() + fUsed);
fUsed += skip;
new (op) T{std::forward<Args>(args)...};
op->type = (uint32_t)T::kType;
op->skip = skip;
return op + 1;
}
struct Op {
uint32_t type : 8;
uint32_t skip : 24;
};
struct DrawPoints final : Op {
static const auto kType = Type::DrawPoints;
DrawPoints(SkCanvas::PointMode mode, size_t count, const SkPaint& paint)
: mode(mode), count(count), paint(paint) {}
SkCanvas::PointMode mode;
size_t count;
SkPaint paint;
void draw(SkCanvas* c, const SkMatrix&) const {
c->drawPoints(mode, count, pod<SkPoint>(this), paint);
}
};
这里重点讲下DisplayListData::push 方法,所有的drawxxx指令都会通过该方法保存起来。
这个方法的实现很有意思,先是申请了一大块内存,然后通过placement new语法初始化对应的指令。然后各个指令按顺序放在这一大块内存中,实现了数组的效果。
也就是说,View的绘制指令被存储在 native层 RecordingCanvas 的 DisplayListData 的 fBytes 中。
整个View树存储在RootRenderNode中。
渲染DisplayList
上面讲完了DisplayList树状结构的构建,接下来要讲的是如何把 DisplayList 渲染出来
在ThreadedRenderer 的 draw方法中,updateRootDisplayList 构建了DisplayList,
syncAndDrawFrame 就是去渲染DisplayList的操作。
// android/view/ThreadedRenderer.java
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
...
// 更新view树节点list,递归构建DisplayList
updateRootDisplayList(view, callbacks);
...
// 交给RenderThread去渲染 syncAndDrawFrame在父类HardwareRenderer中
int syncResult = syncAndDrawFrame(frameInfo);
...
}
// frameworks/base/graphics/java/android/graphics/HardwareRenderer.java
public int syncAndDrawFrame(@NonNull FrameInfo frameInfo) {
return nSyncAndDrawFrame(mNativeProxy, frameInfo.frameInfo, frameInfo.frameInfo.length);
}
// frameworks/base/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz,
jlong proxyPtr, jlongArray frameInfo, jint frameInfoSize) {
LOG_ALWAYS_FATAL_IF(frameInfoSize != UI_THREAD_FRAME_INFO_SIZE,
"Mismatched size expectations, given %d expected %zu", frameInfoSize,
UI_THREAD_FRAME_INFO_SIZE);
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
env->GetLongArrayRegion(frameInfo, 0, frameInfoSize, proxy->frameInfo());
return proxy->syncAndDrawFrame();
}
// RenderProxy 的创建是在 HardwareRenderer初始化的时候创建的
static jlong android_view_ThreadedRenderer_createProxy(JNIEnv* env, jobject clazz,
jboolean translucent, jlong rootRenderNodePtr) {
RootRenderNode* rootRenderNode = reinterpret_cast<RootRenderNode*>(rootRenderNodePtr);
ContextFactoryImpl factory(rootRenderNode);
RenderProxy* proxy = new RenderProxy(translucent, rootRenderNode, &factory);
return (jlong) proxy;
}
切换RenderThread
RenderThread详解
在讲渲染的逻辑之前,这里先详细介绍下RenderThread。
RenderThread是 Android5.0 之后引入的,在Android5.0之前的UI流畅度是远逊色于苹果的,5.0之后,差距极大降低,甚至在一些高端机上已经不遑多让。这都归功于RenderThread,RenderThread极大的降低了主线程的负载,同时还能异步执行动画(这个就很厉害了,主线程被卡住的时候,动画还能流畅执行)
RenderThread具体做了哪些事呢?
首先,GPU绘制上下文的初始化,包括加载EGL的初始化和glContext的获取。
其次,跟主线程一样,增加了任务队列的处理。
最后,添加了vsync机制,可以脱离 UI 线程去独立更新画面,比如一些动画。理论上通过骚操作,我们也可以跳过UI线程去更新界面。
创建时机
可以追溯到 Activity handleResumeActivity,甚至更早,再早就跟渲染没关系,一直到RenderProxy 初始化,初始化的时候首次创建RenderThread实例,并设置为单例。
ActivityThread.handleResumeActivity()
->ViewManager.addView(decor, l)
->WindowManagerImpl.addView()
->WindowManagerGlobal.addView()
->ViewRootImp.setView()
->ViewRootImp.enableHardwareAcceleration()
->ThreadedRenderer.create()
->HardwareRenderer()
->HardwareRenderer.nCreateProxy()
->android_graphics_HardwareRenderer
->android_view_ThreadedRenderer_createProxy
->RenderProxy()
->RenderThread::getInstance()
详细代码如下,除了会创建RenderThread之外,还会创建CanvasContext这个重要对象,RenderThread只是一个子线程,具体的绘画都是由CanvasContext调用opengl或者vulkan完成。
RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode,
IContextFactory* contextFactory)
: mRenderThread(RenderThread::getInstance()), mContext(nullptr) {
mContext = mRenderThread.queue().runSync([&]() -> CanvasContext* {
return CanvasContext::create(mRenderThread, translucent, rootRenderNode, contextFactory);
});
mDrawFrameTask.setContext(&mRenderThread, mContext, rootRenderNode,
pthread_gettid_np(pthread_self()), getRenderThreadTid());
}
CanvasContext 有两种,一种是基于opengl的实现,一种是基vulkan的实现,虽然vulkan的性能会更好一些,但是opengl的使用更加广泛,本次主要讲解opengl的实现。
CanvasContext* CanvasContext::create(RenderThread& thread, bool translucent,
RenderNode* rootRenderNode, IContextFactory* contextFactory) {
auto renderType = Properties::getRenderPipelineType();
switch (renderType) {
case RenderPipelineType::SkiaGL:
return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
std::make_unique<skiapipeline::SkiaOpenGLPipeline>(thread));
case RenderPipelineType::SkiaVulkan:
return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
std::make_unique<skiapipeline::SkiaVulkanPipeline>(thread));
default:
LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t)renderType);
break;
}
return nullptr;
}
调用时机
在上面切换RenderThread的章节已经讲到,在DrawFrameTask::postAndWait()的时候,会将任务抛到RenderThread 中。
void DrawFrameTask::postAndWait() {
AutoMutex _lock(mLock);
// 切换线程
mRenderThread->queue().post([this]() { run(); });
mSignal.wait(mLock);
}
void DrawFrameTask::run() {
...
{
...
canUnblockUiThread = syncFrameState(info);
...
}
...
if (CC_LIKELY(canDrawThisFrame)) {
dequeueBufferDuration = context->draw();
}
...
}
在Run方法里面,还有一些处理异步动画的逻辑,这里就不详细展开了,本次主要分析普通view的一个渲染流程。
比较重要的方法有两个,
一个是 syncFrameState,syncFrameState 的作用是同步渲染数据。
另外一个是 context->draw() ,这个方法会执行真正的渲染操作。下一章将会详细介绍。
bool DrawFrameTask::syncFrameState(TreeInfo& info) {
...
bool canDraw = mContext->makeCurrent();
mContext->unpinImages();
...
mContext->setContentDrawBounds(mContentDrawBounds);
mContext->prepareTree(info, mFrameInfo, mSyncQueued, mTargetNode);
...
}
mContext->makeCurrent()的主要作用是将当前的surface跟opengl绑定,后续通过opengl api 绘制的内容都会到当前window的surface缓存中。
mContext->prepareTree()的作用是遍历整个View树,并同步每个节点的RenderProperties跟displayList的数据。那么这里就有疑问了,为啥不在UI线程把数据同步好了再给RenderThread,答案是为了给UI线程减负。
开始渲染
opengl环境已经准备好,线程也切换成功,数据也准备好了,到了我们最后一步渲染了 context->draw()。
这里我们只讲OpenGL的渲染,所以只需要讲SkiaOpenGLPipeline这个类即可。上面我们在说CanvasContext的时候简单提到过。
nsecs_t CanvasContext::draw() {
...
Frame frame = mRenderPipeline->getFrame();
SkRect windowDirty = computeDirtyRect(frame, &dirty);
bool drew = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue,
mContentDrawBounds, mOpaque, mLightInfo, mRenderNodes,
&(profiler()));
...
bool didSwap =
mRenderPipeline->swapBuffers(frame, drew, windowDirty, mCurrentFrameInfo, &requireSwap);
...
}
Frame SkiaOpenGLPipeline::getFrame() {
LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE,
"drawRenderNode called on a context with no surface!");
return mEglManager.beginFrame(mEglSurface);
}
Frame EglManager::beginFrame(EGLSurface surface) {
LOG_ALWAYS_FATAL_IF(surface == EGL_NO_SURFACE, "Tried to beginFrame on EGL_NO_SURFACE!");
makeCurrent(surface);
Frame frame;
frame.mSurface = surface;
eglQuerySurface(mEglDisplay, surface, EGL_WIDTH, &frame.mWidth);
eglQuerySurface(mEglDisplay, surface, EGL_HEIGHT, &frame.mHeight);
frame.mBufferAge = queryBufferAge(surface);
eglBeginFrame(mEglDisplay, surface);
return frame;
}
bool SkiaOpenGLPipeline::draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
const LightGeometry& lightGeometry,
LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds,
bool opaque, const LightInfo& lightInfo,
const std::vector<sp<RenderNode>>& renderNodes,
FrameInfoVisualizer* profiler) {
...
renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
SkMatrix::I());
...
return true;
}
创建canvas
在上面几个章节讲过,我们业务层用到的canvas都不是真正的canvas,对应的都是 RecordingCanvas,
那么真正用来渲染的canvas是在哪里创建的?
canvas在 SkSurface_Gpu 这个类中创建。SkSurface_Gpu继承自SkSurface这个类。
渲染数据最终都会通过 SkSurface_Gpu 创建的 SkCanvas 执行。
认真读了前面章节的会发现 RecordingCanvas 也是 Skcanvas,兜兜转转怎么又到了 SkCanvas?不会发生死循环吗?
这是因为 RecordingCanvas 重写了 所有的绘制方法,并将绘制指令存了起来,最终交给了 SkCanvas 。
SKcanvas会通过 Opengl渲染相关数据。
void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip,
const std::vector<sp<RenderNode>>& nodes, bool opaque,
const Rect& contentDrawBounds, sk_sp<SkSurface> surface,
const SkMatrix& preTransform) {
...
// capture is enabled.
SkCanvas* canvas = tryCapture(surface.get(), nodes[0].get(), layers);
// draw all layers up front
renderLayersImpl(layers, opaque);
renderFrameImpl(clip, nodes, opaque, contentDrawBounds, canvas, preTransform);
...
}
// external/skia/src/image/SkSurface_Gpu.cpp
SkCanvas* SkSurface_Gpu::onNewCanvas() { return new SkCanvas(fDevice); }
void SkiaPipeline::renderFrameImpl(const SkRect& clip,
const std::vector<sp<RenderNode>>& nodes, bool opaque,
const Rect& contentDrawBounds, SkCanvas* canvas,
const SkMatrix& preTransform) {
...
RenderNodeDrawable root(nodes[0].get(), canvas);
root.draw(canvas);
...
}
void RenderNodeDrawable::onDraw(SkCanvas* canvas) {
// negative and positive Z order are drawn out of order, if this render node drawable is in
// a reordering section
if ((!mInReorderingSection) || MathUtils::isZero(mRenderNode->properties().getZ())) {
this->forceDraw(canvas);
}
}
void RenderNodeDrawable::forceDraw(SkCanvas* canvas) const {
...
// 渲染自身
drawContent(canvas);
...
// 渲染子节点
drawBackwardsProjectedNodes(canvas, *mProjectedDisplayList);
...
}
void RenderNodeDrawable::drawContent(SkCanvas* canvas) const {
}
// 遍历整个RenderNode树
void RenderNodeDrawable::drawBackwardsProjectedNodes(SkCanvas* canvas,
const SkiaDisplayList& displayList,
int nestLevel) const {
...
for (auto& child : displayList.mChildNodes) {
...
// immediate children cannot be projected on their parent
if (childProperties.getProjectBackwards() && nestLevel > 0) {
SkAutoCanvasRestore acr2(canvas, true);
// Apply recorded matrix, which is a total matrix saved at recording time to avoid
// replaying all DL commands.
canvas->concat(child.getRecordedMatrix());
child.drawContent(canvas);
}
...
if (0 == nestLevel || !displayList.containsProjectionReceiver()) {
...
const RenderNode* childNode = child.getRenderNode();
...
const SkiaDisplayList* childDisplayList = childNode->getDisplayList().asSkiaDl();
if (childDisplayList) {
drawBackwardsProjectedNodes(canvas, *childDisplayList, nestLevel + 1);
}
}
}
}
child.drawContent(canvas)会执行DisplayListData的draw方法。
解析DisplayListData指令并执行
在 添加绘制指令 那一个章节讲到,我们的绘制指令都被放在一块连续的内存之中,终于到了解析它的环节了。和它的添加操作一样,解析的写法也很有趣,非常的解耦,就是阅读性稍微差了些。
typedef void (*draw_fn)(const void*, SkCanvas*, const SkMatrix&);
#define X(T) \
[](const void* op, SkCanvas* c, const SkMatrix& original) { \
((const T*)op)->draw(c, original); \
},
static const draw_fn draw_fns[] = {
#include "DisplayListOps.in"
};
#undef X
// "DisplayListOps.in"
X(Flush)
X(Save)
X(Restore)
X(SaveLayer)
X(SaveBehind)
X(Concat)
X(SetMatrix)
X(Scale)
X(Translate)
X(ClipPath)
X(ClipRect)
X(ClipRRect)
X(ClipRegion)
X(DrawPaint)
X(DrawBehind)
X(DrawPath)
X(DrawRect)
X(DrawRegion)
X(DrawOval)
X(DrawArc)
X(DrawRRect)
X(DrawDRRect)
X(DrawAnnotation)
X(DrawDrawable)
X(DrawPicture)
X(DrawImage)
X(DrawImageRect)
X(DrawImageLattice)
X(DrawTextBlob)
X(DrawPatch)
X(DrawPoints)
X(DrawVertices)
X(DrawAtlas)
X(DrawShadowRec)
X(DrawVectorDrawable)
X(DrawRippleDrawable)
X(DrawWebView)
// 渲染自身的画布
void DisplayListData::draw(SkCanvas* canvas) const {
SkAutoCanvasRestore acr(canvas, false);
this->map(draw_fns, canvas, canvas->getTotalMatrix());
}
inline void DisplayListData::map(const Fn fns[], Args... args) const {
auto end = fBytes.get() + fUsed;
for (const uint8_t* ptr = fBytes.get(); ptr < end;) {
auto op = (const Op*)ptr;
auto type = op->type;
auto skip = op->skip;
if (auto fn = fns[type]) { // We replace no-op functions with nullptrs
fn(op, args...); // to avoid the overhead of a pointless call.
}
ptr += skip;
}
}
SkCanvas 执行指令
这里以前几章的 DrawPoints 为例分析,最终会执行 DrawPoints的draw方法
struct DrawPoints final : Op {
static const auto kType = Type::DrawPoints;
DrawPoints(SkCanvas::PointMode mode, size_t count, const SkPaint& paint)
: mode(mode), count(count), paint(paint) {}
SkCanvas::PointMode mode;
size_t count;
SkPaint paint;
void draw(SkCanvas* c, const SkMatrix&) const {
c->drawPoints(mode, count, pod<SkPoint>(this), paint);
}
};
void SkCanvas::drawPoints(PointMode mode, size_t count, const SkPoint pts[], const SkPaint& paint) {
TRACE_EVENT0("skia", TRACE_FUNC);
this->onDrawPoints(mode, count, pts, paint);
}
void SkCanvas::onDrawPoints(PointMode mode, size_t count, const SkPoint pts[],
const SkPaint& paint) {
...
this->topDevice()->drawPoints(mode, count, pts, layer.paint());
}
void SkGpuDevice::drawPoints(SkCanvas::PointMode mode,
size_t count, const SkPoint pts[], const SkPaint& paint) {
...
fSurfaceDrawContext->drawVertices(this->clip(), std::move(grPaint), *matrixProvider,
std::move(vertices), &primitiveType);
...
}
分析到 drawVertices 这里也差不多了,再往后分析的话,就牵扯到openGL相关的知识,本篇就不再深入探讨。
上屏显示
nsecs_t CanvasContext::draw() {
...
Frame frame = mRenderPipeline->getFrame();
SkRect windowDirty = computeDirtyRect(frame, &dirty);
bool drew = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue,
mContentDrawBounds, mOpaque, mLightInfo, mRenderNodes,
&(profiler()));
...
bool didSwap =
mRenderPipeline->swapBuffers(frame, drew, windowDirty, mCurrentFrameInfo, &requireSwap);
...
}
bool SkiaOpenGLPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty,
FrameInfo* currentFrameInfo, bool* requireSwap) {
...
currentFrameInfo->markSwapBuffers();
*requireSwap = drew || mEglManager.damageRequiresSwap();
if (*requireSwap && (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty)))) {
return false;
}
return *requireSwap;
}
int ANativeWindow_queueBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer, int fenceFd) {
return window->queueBuffer(window, buffer, fenceFd);
}
nt Surface::queueBuffer(android_native_buffer_t* buffer, int fenceFd) {
...
IGraphicBufferProducer::QueueBufferOutput output;
IGraphicBufferProducer::QueueBufferInput input;
getQueueBufferInputLocked(buffer, fenceFd, mTimestamp, &input);
sp<Fence> fence = input.fence;
nsecs_t now = systemTime();
status_t err = mGraphicBufferProducer->queueBuffer(i, input, &output);
mLastQueueDuration = systemTime() - now;
if (err != OK) {
ALOGE("queueBuffer: error queuing buffer, %d", err);
}
onBufferQueuedLocked(i, fence, output);
return err;
}
通过Opengl渲染完成之后,需要调用mEglManager.swapBuffers上屏,这里后续很多代码源码中看不到,都是厂商自定义的一些实现。最终会调用到 ANativeWindow_dequeueBuffer,而 ANativeWindow_dequeueBuffer 被Surface::queueBuffer hook,最终会执行 mGraphicBufferProducer->queueBuffer(i, input, &output);
Android12之前,这是一个IPC 通信,会通知 SurfaceFlinger 进行合成并显示。
Android12及其之后,为了降低SurfaceFlinger的压力,buffer的生产与消费都放到了 App 内部,不再需要 跨进程通信,不管怎样,这个方法最终也会触发 SurfaceComposerClient 跨进程通信,将渲染好的 buffer 传给 SurfaceFlinger 去合成。
与SurfaceFlinger的建联流程
看过ViewRootImp源码的同学可能会对 Surface有点印象,它跟 SurfaceControl 都是通过 new的方式创建出来的。
实际上,这里只是创建了空壳子,实际上的 Surface 创建非常繁琐。
public final class ViewRootImpl{
...
@UnsupportedAppUsage
public final Surface mSurface = new Surface();
private final SurfaceControl mSurfaceControl = new SurfaceControl();
...
}
首先,Surface 的创建需要依赖 SurfaceControl,new SurfaceControl() 也是创建了一个空壳,SurfaceControl 的创建也很繁琐。
SurfaceControl 的创建
SurfaceControl 真正创建的时机是在 ViewRootImpl.performTraversals->ViewRootImpl.relayoutWindow 中。
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
boolean insetsPending) throws RemoteException {
...
int relayoutResult = mWindowSession.relayout(mWindow, params,
(int) (mView.getMeasuredWidth() * appScale + 0.5f),
(int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
mTempControls, mSurfaceSize);
...
}
class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
...
@Override
public int relayout(IWindow window, WindowManager.LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewFlags, int flags, long frameNumber,
ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
SurfaceControl outSurfaceControl, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls, Point outSurfaceSize) {
...
int res = mService.relayoutWindow(this, window, attrs,
requestedWidth, requestedHeight, viewFlags, flags, frameNumber,
outFrames, mergedConfiguration, outSurfaceControl, outInsetsState,
outActiveControls, outSurfaceSize);
...
return res;
}
...
}
mWindowSession.relayout是一个 IPC 方法,最终会调用到 WindowManagerService 里的 relayoutWindow 方法,后面的流程就不分析了,最终会在WMS 里创建一个 SurfaceControl,并将其内容 copy到 ViewRootImpl 中的mSurfaceControl。
public class WindowManagerService extends IWindowManager.Stub {
public int relayoutWindow(Session session, IWindow client, LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewVisibility, int flags,
long frameNumber, ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
SurfaceControl outSurfaceControl, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls, Point outSurfaceSize) {
...
result = createSurfaceControl(outSurfaceControl, result, win, winAnimator);
...
}
}
status_t SurfaceComposerClient::createSurfaceChecked(const String8& name, uint32_t w, uint32_t h,
PixelFormat format,
sp<SurfaceControl>* outSurface, uint32_t flags,
const sp<IBinder>& parentHandle,
LayerMetadata metadata,
uint32_t* outTransformHint) {
...
err = mClient->createSurface(name, w, h, format, flags, parentHandle, std::move(metadata),
...
}
status_t Client::createSurface(const String8& name, uint32_t w, uint32_t h, PixelFormat format,
uint32_t flags, const sp<IBinder>& parentHandle,
LayerMetadata metadata, sp<IBinder>* handle,
sp<IGraphicBufferProducer>* gbp, int32_t* outLayerId,
uint32_t* outTransformHint) {
// We rely on createLayer to check permissions.
return mFlinger->createLayer(name, this, w, h, format, flags, std::move(metadata), handle, gbp,
parentHandle, outLayerId, nullptr, outTransformHint);
}
status_t SurfaceComposerClient::createSurfaceChecked(const String8& name, uint32_t w, uint32_t h,
PixelFormat format,
sp<SurfaceControl>* outSurface, uint32_t flags,
const sp<IBinder>& parentHandle,
LayerMetadata metadata,
uint32_t* outTransformHint) {
...
*outSurface =
new SurfaceControl(this, handle, gbp, id, w, h, format, transformHint, flags);
...
}
Surface的创建
sp<Surface> SurfaceControl::generateSurfaceLocked()
{
uint32_t ignore;
auto flags = mCreateFlags & (ISurfaceComposerClient::eCursorWindow |
ISurfaceComposerClient::eOpaque);
mBbqChild = mClient->createSurface(String8("bbq-wrapper"), 0, 0, mFormat,
flags, mHandle, {}, &ignore);
mBbq = sp<BLASTBufferQueue>::make("bbq-adapter", mBbqChild, mWidth, mHeight, mFormat);
// This surface is always consumed by SurfaceFlinger, so the
// producerControlledByApp value doesn't matter; using false.
mSurfaceData = mBbq->getSurface(true);
return mSurfaceData;
}
sp<Surface> BLASTBufferQueue::getSurface(bool includeSurfaceControlHandle) {
std::unique_lock _lock{mMutex};
sp<IBinder> scHandle = nullptr;
if (includeSurfaceControlHandle && mSurfaceControl) {
scHandle = mSurfaceControl->getHandle();
}
return new BBQSurface(mProducer, true, scHandle, this);
}
最终会获得一个 BBQSurface 对象。上一章上屏显示里讲过,最终通过该对象调用 queueBuffer,将渲染完的数据传给SurfaceFlinger。
总结
单个App内的渲染流程到这就算是结束了,后续 SurfaceFlinger 合成的逻辑比较独立,会单独开一篇文章讨论。
读完这里的源码之后我的收获如下:
-
渲染的整个链路算是理清楚了,如果让我设计一套渲染系统,心里是有点底了。
-
虽然源码很多细节写的不是很好,但整体的框架设计还是非常的解耦,值得借鉴。
-
应用层合理利用 RenderThread 可以提高 App 的流畅度