Android 系统渲染那些事(一)

893 阅读17分钟

前言

为什么要做这么一篇分享?

首先是以前对 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一个共享缓冲区,可以跨进程、跨硬件调用。
layerSurfaceFlinger 里面的 surface,与应用层的 surface 一一对应。Android12之前,共享缓存也是由 layer 去创建的。
HWComposer硬件组合抽象层,由厂商实现,不同的厂商实现是不一样的,主要就是为了 SurfaceFlinger 提供硬件支持(非GPU)。
RenderThreadAndroid5.0 为了降低主线程的压力,将一些在UI线程中执行的动作移到了一个子线程,这个子线程就是RenderThread,从而实现了异步更新UI的能力。

应用层通过 canvas view树 渲染到缓存中,然后通过 **surface **将自己的渲染数据放入 Buffer Queue 的共享内存,最终通过 binder 通知 surfaceflinger 进行合成,接下来会详细介绍 App内的渲染细节。

image.png

普通view的渲染流程

Vsync 跟编舞者的更新流程可以参考我的另外一篇文章 juejin.cn/post/741577…

image.png

本篇文章会重点分析 ThreadRender.draw 之后的操作。

View树的更新与渲染

在看这块代码的时候,一直有一个点困惑着我,树状结构到底在哪,源码翻了好几遍才找到。在下面我会详细介绍。

所以在详细分析这里流程之前,先简单介绍下这里的树状结构。

树状结构介绍

在 java 层看不到树状结构,树状结构在native 层,并且树状结构是通过RenderNode 、DisplayList 、RenderNodeDrawable 这三个类组成。

树状结构

(下面都是native层的类)

  • RootRenderNode

相关类图

image.png

相关类的解释:

类名说明
RenderNode渲染节点,保存了view相关的属性,持有 SkiaDisplayList。Java层的RendeNode 会持有 SkiaRecordingCanvas 对象。Native层的不持有。
RootRenderNode根节点对应的RenderNode,RootRenderNode,继承自RenderNode。在HardwareRenderer 初始化的时候创建。
SkiaRecordingCanvas硬件渲染的画布,在硬件加速逻辑中的canvas都是它,持有RenderNode 的 SkiaDisplayList ,RenderNode添加与移除子节点的操作都是由SkiaRecordingCanvas来完成,同时所有canvas.drawxxx()方法都会转换成 DisplayListData,并放置到SkiaDisplayList中。是构建View树背后的英雄。
RecordingCanvasJava层跟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 的流畅度