聊聊Android渲染流程

353 阅读3分钟

创建流程

  • 应用程序窗口对应一个Surface(Surface.java)
  • Surface由每个应用进程的ViewRootImpl持有
  • Surface最初初始化为一个空Surface
  • Surface的初始化发生在第一次relayoutWindow
[View.invalidate][逐级传递到 ViewRootImpl][ViewRootImpl.scheduleTraversals()][插入同步屏障,注册 VSYNC 回调][VSYNC 信号到达 → 执行 doTraversal() → performTraversals() → relayoutWindow][执行updateBlastSurfaceIfNeeded()][创建BLASTBufferQueue → mBlastBufferQueue.createSurface() → mSurface.transferFrom(blastSurface)]

接下来分析Java层BLASTBufferQueue的createSurface创建流程

BLASTBufferQueue.createSurface

  • Java层的createSurface对应到jni中的nativeGetSurface
  • jni层中,通过BLASTBufferQueue::getSurface获取Surface
  • 在BLASTBufferQueue::getSurface中创建并返回了一个BBQSurface
sp<Surface> BLASTBufferQueue::getSurface(bool includeSurfaceControlHandle) {
    std::lock_guard _lock{mMutex};
    sp<IBinder> scHandle = nullptr;
    if (includeSurfaceControlHandle && mSurfaceControl) {
        scHandle = mSurfaceControl->getHandle();
    }
    return new BBQSurface(mProducer, true, scHandle, this);
}
  • BBQSurface关联了BBQ中的生产者、SurfaceControl、还有BBQ自己本身
  • Java层窗口对应的Surface在native层包装的是BBQSurface

将Surface设置给hwui

  • 接着上一节,在performTraversals()中创建Surface后,接着会进行更新Surface
private void performTraversals() {
    ...
    } else if ((surfaceReplaced || surfaceSizeChanged || updateSurfaceNeeded)
                        && mSurfaceHolder == null
                        && mAttachInfo.mThreadedRenderer != null
                        && mSurface.isValid()) {
        mFullRedrawNeeded = true;
        try {
            // Need to do updateSurface (which leads to CanvasContext::setSurface and
            // re-create the EGLSurface) if either the Surface changed (as indicated by
            // generation id), or WindowManager changed the surface size. The latter is
            // because on some chips, changing the consumer side's BufferQueue size may
            // not take effect immediately unless we create a new EGLSurface.
            // Note that frame size change doesn't always imply surface size change (eg.
            // drag resizing uses fullscreen surface), need to check surfaceSizeChanged
            // flag from WindowManager.
            mAttachInfo.mThreadedRenderer.updateSurface(mSurface);
        } catch (OutOfResourcesException e) {
            handleOutOfResourcesException(e);
            mLastPerformTraversalsSkipDrawReason = "oom_update_surface";
            return;
        }
    }
}
  • 从java层一层层调用到SkiaPipeline
  • 需要注意的一点是在jni层,会将Surface转成ANativeWindow,在hwui模块中只能感知到ANativeWindow的存在
  • 在skiapipeline中根据opengl、vulkan后端,区分不同的流程(Android15中已经默认走vulkan)
    • (opengl),在SkiaOpenGLPipeline中调用setSurface,然后是EglManager.createSurace将ANativeWindow转成EGLSurface

渲染

  • 还有一条非常隐蔽的线,在SkiaPipeline renderFrame渲染每帧内容时,用的是SkSurface,SkSurface与EGLSurface的关联是源码中看不到的
  • 在SkiaPipeline中通过MakeFromBackendRenderTarget创建SkSurface时,需要传入GrBackendRenderTarget(封装了 EGLSurface 的帧缓冲区对象 ​FBO
  • 应用通过SkSurfaceSkCanvas进行绘制,生成GPU命令
  • 绘制完后,Skia调用GrContext::flush,将GPU命令提交到EGLSurface的帧缓冲区
  • 调用 eglSwapBuffers 或 eglSwapBuffersWithDamageKHR 交换前后缓冲区,触发 BufferQueue 的 queueBuffer 操作,将渲染后的 Buffer 提交给 SurfaceFlinger 合成
  • 小结一下:ANativeWindow 与 SkSurface 的关联通过以下链路实现:
    ANativeWindow → EGLSurface(OpenGL ES 窗口目标) → GrBackendRenderTarget(Skia 后端渲染目标) → SkSurface(绘图表面)​
    整个过程依赖 ​EGL 的桥接作用 和 ​GrContext 的上下文管理,最终将 Skia 的 GPU 渲染结果提交到应用窗口的 BufferQueue,实现画面显示

Buffer轮转

BufferQueue的工作流程网上很多文章都做过介绍,这里不再赘述:

  • ANativeWindow本身是BBQSurface,关联了BBQ,BufferQueue会有Buffer轮转
  • 当应用通过 OpenGL ES 完成一帧的绘制后调用 eglSwapBuffers,EGL 内部会通过绑定的 ANativeWindow(如 Surface)触发 queueBuffer,将当前已填充数据的 Buffer ​提交给消费者​(如 SurfaceFlinger
  • 在 eglSwapBuffers 内部,EGL 还会通过 ANativeWindow 调用 dequeueBuffer,从 BufferQueue 中 ​获取一个空闲的 Buffer​(状态从 FREE → DEQUEUED),用于下一帧的渲染
  • 关键代码流程如下:
void CanvasContext::draw(bool solelyTextureViewUpdates) {
    ...
    IRenderPipeline::DrawResult drawResult;
    {
        // FrameInfoVisualizer accesses the frame events, which cannot be mutated mid-draw
        // or it can lead to memory corruption.
        // This lock is overly broad, but it's the quickest fix since this mutex is otherwise
        // not visible to IRenderPipeline much less FrameInfoVisualizer. And since this is
        // the thread we're primarily concerned about being responsive, this being too broad
        // shouldn't pose a performance issue.
        std::scoped_lock lock(mFrameMetricsReporterMutex);
        drawResult = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry,
                                           &mLayerUpdateQueue, mContentDrawBounds, mOpaque,
                                           mLightInfo, mRenderNodes, &(profiler()), mBufferParams);
    }
    ...
    bool didSwap = mRenderPipeline->swapBuffers(frame, drawResult.success, windowDirty,
                                                mCurrentFrameInfo, &requireSwap);
}