Android硬件渲染流程

一.渲染流程

1.VSync信号的监听

    在Android中,App的渲染流程是从ViewRootImpl开始的。在回调Activity的onResume方法后,会调用ViewRootImpl的requestLayout方法触发页面中View的测量与绘制。

    在requestLayout方法中,首先会调用checkThread方法检查当前线程是否为UI线程,如果不是,则抛出异常。接下来会调用scheduleTraversals方法。
0
    在ViewRootImpl的scheduleTraversals方法中,主要做了两件事:

1)向主线程的消息队列发送一个同步信息屏障。

2)提交callbackType类型为CALLBACK_TRAVERSAL的TraversalRunnable。
1

2.VSync信号触发绘制

    当VSync信号到来时,会执行TraversalRunnable的run方法,该方法内部会调用ViewRootImpl的doTraversal方法。
2
    在ViewRootImpl的doTraversal方法中,主要做了两件事:

1)移除主线程消息队列的同步信息屏障。

2)调用performTraversals方法。
3
    在ViewRootImpl的performDraw方法中,会调用draw方法。在ViewRootImpl的draw方法中,如果开启了硬件渲染,就会从mAttachInfo的mThreadedRenderer中获取ThreadedRenderer。并调用ThreadedRenderer的draw方法。
4
    在ThreadedRenderer的draw方法中主要做了两件事:

1)从DecorView开始递归构建DisplayList。

2)唤醒Render线程对DisplayList进行渲染。
5
    在ThreadedRenderer的updateRootDisplayList中主要做了四件事:

1)从DecorView开始向下分发draw方法,递归构建DisplayListOp。

2)获取最顶层的RecordingCanvas。

3)通过DecorView获取最终的RenderNode并绘制到RecordingCanvas上。

4)将DisplayListOp填充到Native层的RootRenderNode中。
6

二.渲染原理

1.画布的获取

    在硬件渲染中,每个View都有一个RenderNode。当调用RenderNode的beginRecording方法时,内部会调用RecordingCanvas的静态方法obtain获取RenderNode。
7    在RecordingCanvas的静态方法obtain中,会创建RecordingCanvas。
8
    在RecordingCanvas的构造方法中,主要做了两件事:

1)创建Native层Canvas并返回对应的地址。

2)调用父类的构造方法对返回的地址进行保存。
9

1.1 画布的创建

    RecordingCanvas的nCreateDisplayListCanvas方法对应的Native实现为android_graphics_DisplayListCanvas的android_view_DisplayListCanvas_createDisplayListCanvas函数。

    在android_view_DisplayListCanvas_createDisplayListCanvas函数中,主要做了三件事:

1)获取Native层的RenderNode。

2)创建Canvas。

3)返回Canvas对应的地址。
10
    在Canvas::create_recording_canvas方法中,会创建SkiaRecordingCanvas。
11
    在SkiaRecordingCanvas的构造方法中,会调用initDisplayList方法,初始化DisplayList。
12

1.2 渲染指令列表的创建

    在SkiaRecordingCanvas的initDisplayList方法中,主要做了三件事:

1)清除上一帧的SkiaDisplayList,如果SkiaDisplayList为空,则再创建一个SkiaDisplayList。

2)绑定SkiaDisplayList与RecordingCanvas。

3)将RecordingCanvas保存到SkiaCanvas中。
13
    在SkiaDisplayList的attachRecorder方法中,会将SkiaDisplayList中的SkiaDisplayData与RecordingCanvas绑定。
14

2.绘制与渲染指令

2.1 矩形的绘制

    在硬件绘制过程中,当调用Canvas的drawRect方法时,在Canvas的drawRect方法中,会调用nDrawRect方法。
15
    Canvas的nDrawRect方法对应的Native实现为android_graphics_Canvas的drawRect函数。在drawRect函数中,主要做了两件事:

1)获取Native层Canvas。

2)通过Canvas绘制矩形。
16
    SkiaRecordingCanvas继承自SkiaCanvas。这里的drawRect方法在SkiaRecordingCanvas的父类SkiaCanvas中实现。

    在SkiaCanvas的drawRect方法中,会调用SkCanvas的drawRect方法。
17
    在SkCanvas的drawRect方法中,会调用子类RecordingCanvas的onDrawRect方法。
18
    在RecordingCanvas的onDrawRect方法中,会调用之前保存的DisplayListData的drawRect方法。在DisplayListData的drawRect方法中,会创建一个DrawRect指令并保存。
19

2.2 硬件渲染指令

    在DisplayListData中,所有的绘制指令都存储在一块连续的内存中。

    在Android中,所有的绘制指令都继承了Op。Op是一个结构体,在Op中有两个字段,type表示指令的类型,skip表示指令的长度。

struct Op {
    uint32_t type : 8;
    uint32_t skip : 24;
  };

    在DisplayListData的push方法中,主要做了四件事:

1)计算当前指令的长度。

2)判断所有的绘制指令是否超过内存最大容量,如果超过最大容量,则进行扩容,重新分配内存,每次扩容增加4096个字节。

3)计算当前绘制指令在内存中的位置,并在指定位置处创建当前指令的实例对象。

4)为当前绘制指令的type和skip赋值。
20

2.3 节点的绘制

    在ThreadedRenderer的updateViewTreeDisplayList方法中,会调用View的updateDisplayListIfDirty方法。
21    在View的updateDisplayListIfDirty方法中,主要做了三件事:

1)从自身的RenderNode中获取RecordingCanvas。

2)通过Flag判断,如果是ViewGroup且自身不用绘制,则分发子View去绘制,否则直接绘制。

3)结束绘制。
22
    在View的draw方法中,首先会绘制自身。然后对子View进行绘制。dispatchDraw方法在View中为空实现,如果一个View不是ViewGroup,那么dispatchDraw方法不会对任何View进行绘制分发。
23
    View的dispatchDraw方法在ViewGroup中被重写。如果一个View是ViewGroup,那么dispatchDraw方法会对子View进行绘制分发。在ViewGroup的dispatchDraw方法中,会调用drawChild方法。
24
    在ViewGroup的drawChild方法中,会调用View的draw方法。这里的draw方法是View中一个重载的draw方法,只在ViewGroup中调用。
25
    在View重载的draw方法中,主要做了三件事:

1)判断是否开启硬件渲染。

2)如果开启硬件渲染,则对当前View构建DisplayList,保存到当前View的RenderNode并返回。

3)如果当前View的DisplayList不为空,则将当前View的RenderNode绘制到父View的Canvas上。
26
    在RecordingCanvas的drawRenderNode方法中,会调用nDrawRenderNode方法。
27
    RecordingCanvas的nDrawRenderNode方法对应的Native层实现为android_graphics_DisplayListCanvas的android_view_DisplayListCanvas_drawRenderNode函数。

    在android_view_DisplayListCanvas_drawRenderNode函数中,主要做了三件事:

1)根据地址获取Native层Canvas。

2)根据地址获取View对应的Native层的RenderNode。

3)将RenderNode绘制到Canvas上。
28
    根据硬件渲染中Canvas的创建过程可以知道,这里的Canvas实际上是SkiaRecordingCanvas。

    在SkiaRecordingCanvas的drawRenderNode方法中,主要做了两件事:

1)将RenderNode封装成RenderNodeDrawable,保存到SkiaDisplayList中用于记录。

2)对封装好的RenderNodeDrawable进行绘制。
29
    SkiaRecordingCanvas继承自SkiaCanvas。这里的drawDrawable方法在SkiaRecordingCanvas的父类SkiaCanvas中实现。

    在SkiaCanvas的drawDrawable方法中,会调用SkCanvas的drawDrawable方法。
30
    在SkCanvas的drawDrawable方法中,会调用子类RecordingCanvas的onDrawDrawable方法。
31
    在RecordingCanvas的onDrawDrawable方法中,会调用之前保存的DisplayListData的drawDrawable方法。在DisplayListData的drawDrawable方法中,会创建一个DrawDrawable指令并保存。
32

3.绘制的提交

3.1 绘制结果的保存

    在RenderNode的endRecording方法中,主要做了两件事:

1)停止记录,标记DisplayList可用。

2)释放RecordingCanvas。
33
    在RecordingCanvas的finishRecording方法中,会调用nFinishRecording方法。
34
    RecordingCanvas的nFinishRecording方法对应的Native实现为android_graphics_DisplayListCanvas的android_view_DisplayListCanvas_finishRecording函数。

    在android_view_DisplayListCanvas_finishRecording函数中,主要做了三件事:

1)获取Canvas,实际获取的是SkiaRecordingCanvas。

2)获取RenderNode。

3)调用SkiaRecordingCanvas的finishRecording结束绘制。
35
    在SkiaRecordingCanvas的finishRecording方法中,主要做了三件事:

1)暂停标记并获取SkiaDisplayList。

2)将SkiaDisplayList封装成DisplayList,DisplayList是对SkiaDisplayListWrapper的重命名,SkiaDisplayListWrapper会保存SkiaDisplayList。

3)将DisplayList保存到RenderNode中。
36

3.2 绘制结果的获取

    在DisplayList构建完成后, 会调用ThreadedRenderer的syncAndDrawFrame方法唤醒Render线程进行渲染。在ThreadedRenderer的syncAndDrawFrame中,会调用nSyncAndDrawFrame方法。
37
    ThreadedRenderer的nSyncAndDrawFrame方法对应的Native实现为android_graphics_HardwareRenderer的android_view_ThreadedRenderer_syncAndDrawFrame函数。

    在android_view_ThreadedRenderer_syncAndDrawFrame函数中,主要做了两件事:

1)获取RenderProxy。

2)调用RenderProxy的syncAndDrawFrame方法。
38
    在RenderProxy的syncAndDrawFrame方法中,会调用DrawFrameTask的drawFrame方法。在DrawFrameTask的drawFrame方法中,会调用postAndWait方法。
39
    在DrawFrameTask的postAndWait方法中,主要做了三件事:

1)对DrawFrameTask的run方法进行封装。

2)将封装后的对象添加到RenderThread的队列中。

3)UI线程进入阻塞状态。
40
    当RenderTread执行任务时,会调用DrawFrameTask的run方法。在DrawFrameTask的run方法中,主要做了三件事:

1)获取UI线程构建的DisplayList。

2)唤醒UI线程。

3)根据DisplayList进行绘制。
41

4.层级的构建

    在DrawFrameTask的syncFrameState方法中,主要做了两件事:

1)处理硬件加速层,如TextureView的绘制。

2)构建TreeInfo。
42
    在CanvasContext的prepareTree方法中,主要做了两件事:

1)保存LayerUpdateQueue到TreeInfo中,为后续后构建TreeInfo做准备。LayerUpdateQueue用于保存待绘制的RenderNode。

2)遍历RenderNode构建TreeInfo。
43
    在RenderNode的prepareTreeImpl方法中,主要做了三件事:

1)获取DisplayList。

2)通过DisplayList分发子节点构建TreeInfo。

3)将完成构建的当前RenderNode保存到LayerUpdateQueue中。
44

4.1 绘制结果的更新

    在RenderNode的pushStagingDisplayListChanges方法中,会调用syncDisplayList方法,对DisplayList进行锁定保存。
45
    在RenderNode的syncDisplayList方法中,主要做了三件事:

1)遍历mStagingDisplayList中保存的RenderNode,对RednerNode的引用加1。mStagingDisplayList是用于暂存本次构建好的DisplayList的变量。

2)遍历mDisplayList中保存的RenderNode,对RednerNode的引用减1,并清空mDisplayList中对SkiaDisplayList的引用。mDisplayList是用于保存下次待渲染的DisplayList的变量。

3)将本次构建好的DisplayList保存到下次待渲染的DisplayList。
46
    DisplayList是对SkiaDisplayListWrapper的重命名。在SkiaDisplayListWrapper的updateChildren方法中,会调用SkiaDisplayList的updateChildren方法。
47
    在SkiaDisplayList的updateChildren方法中,主要做了三件事:

1)遍历获取RenderNodeDrawable。

2)从RenderNodeDrawable中获取RenderNode。

3)将RenderNode作为参数,执行参数中传入的function方法。
48

4.2 构建的分发

    DisplayList是对SkiaDisplayListWrapper的重命名。在SkiaDisplayListWrapper的prepareListAndChildren方法中,会调用SkiaDisplayList的prepareListAndChildren方法。
49
    在SkiaDisplayList的prepareListAndChildren方法中,主要做了四件事:

1)遍历获取RenderNodeDrawable。

2)从RenderNodeDrawable中获取RenderNode。

3)对TreeInfo中的属性进行更新。

4)将RenderNode和TreeInfo作为参数,执行参数中传入的function方法。
50
    这里传入的function是RenderNode的prepareTreeImpl方法,这样就实现了子RenderNode构建TreeInfo。

4.3 构建数据的保存

    在RenderNode的pushLayerUpdate方法中,会对当前的RenderNode进行保存。
51

5.渲染与渲染管线

    在Android中,渲染管线有两种:SkiaOpenGLPipeline和SkiaVulkanPipeline,底层实现分别对应着OpenGL和Vulkan。下面所有的IRenderPipeline以SkiaOpenGLPipeline为例。

    在CanvasContext的draw方法中,主要做了三件事:

1)获取可绘制的缓存。

2)使用GPU按照绘制指令绘制界面。

3)将绘制好的图形缓冲通过Binder交给SurfaceFlinger进行合成与显示,即上帧。
52
    在SkiaOpenGLPipeline的draw方法中,主要做了两件事:

1)创建SkSurface指针并初始化。

2)开始渲染。
53

    SkiaOpenGLPipeline的renderFrame方法在SkiaOpenGLPipeline的父类SkiaPipeline中实现。

    在SkiaPipeline的renderFrame方法中,主要做了四件事:

1)获取SkCanvas。

2)处理发生变化的Layer,更新对应RenderNode。

3)对所有的renderNode进行绘制。

4)释放SkCanvas。
54

5.1 节点的更新

    在SkiaPipeline的renderLayersImpl方法中,主要做了三件事:

1)遍历LayerUpdateQueue,获取Entry,并从Entry中获取RenderNode.

2)从RenderNode中获取SkCanvas。

3)将RenderNode分装成RenderNodeDrawable,并绘制到SkCanvas上。
55
    RenderNodeDrawable继承自SkDrawable。在RenderNodeDrawable的draw方法中,会调用onDraw方法,onDraw方法在RenderNodeDrawable被重写。在RenderNodeDrawable的onDraw方法中,会调用forceDraw方法。在RenderNodeDrawable的forceDraw方法中,会调用drawContent方法。
56
    在RenderNodeDrawable的drawContent方法中,主要做了两件事:

1)从RenderNode中获取DisplayList。

2)绘制DisplayList。
57
    在SkiaDisplayList的draw方法中,会调用DisplayListData的draw方法。
58
    在DisplayListData的draw方法中,会调用map方法。在DisplayListData的map方法中,主要做了五件事:

1)计算绘制指令内存中绘制指令的终止区域。

2)从绘制指令内存的起始位置进行遍历。

3)获取绘制指令的类型和长度。

4)根据指令的类型,调用指令的绘制方法。

5)根据指令的长度,计算下一个绘制指令的位置。
59
    Array<Fn>是模版生成的代码,其中的模版参数Fn代表不同类型的绘制指令的draw方法的调用。以DrawRect指令为例,当ptr指针指向DrawRect指令时,会根据指令类型从Array<Fn>取出对应DrawRect的draw方法调用的Fn,当调用Fn时,会触发DrawRect的draw方法的执行。

    在DrawRect的draw方法中,会通过SkCanvas的draw方法完成绘制。
60

5.2 节点的渲染

    在SkiaPipeline的renderFrameImpl方法中,主要做了两件事:

1)对RenderNode进行遍历,将RenderNode封装成RenderNodeDrawable。

2)对RenderNodeDrawable进行渲染。
61
    与renderLayerImpl不同,renderFrameImpl绘制到缓存对应的SkCanvas上,而不是自身的SkCanvas上。

三.总结

1.硬件渲染

    Android硬件渲染分成两个部分:渲染指令列表的构建和渲染指令列表的渲染,分别对应着ThreadedRenderer的updateRootDisplayList方法和syncAndDrawFrame方法。即绘制过程和渲染过程是分开的。

    绘制过程发生在UI线程,渲染过程发生在Render线程。

2.绘制流程

    在硬件渲染中,每个View对应着一个RenderNode。每个RenderNode中保存着对应的SkiaDisplayList。

    硬件的绘制过程,本质上是将待绘制的数据封装成对应的硬件渲染指令,保存到SkiaDisplayList中。子View绘制完成后,父View会对子View的RenderNode进行绘制,将子View的RenderNode绘制指令保存到SkiaDisplayList中,形成绘制的层级结构。

    画布的获取本质上是对SkiaDisplayList的创建与初始化。

    绘制的提交本质上是将构建好的SkiaDisplayList保存到RenderNode中。

2.1 渲染指令

    在Android中,所有的绘制指令都继承了Op。Op是一个结构体,在Op中有两个字段,type表示指令的类型,skip表示指令的长度。

struct Op {
    uint32_t type : 8;
    uint32_t skip : 24;
  };

    所有的渲染指令都保存到DisplayListData中,DisplayListData本质上是一块连续的内存。

2.2 对应关系

    SkiaRecordingCanvas继承自SkiaCanvas,RecordingCanvas继承自SkCanvas。

    RecordingCanvas负责管理硬件渲染指令和DisplayListData、操作硬件渲染指令在DisplayListData上的填充。SkiaRecordingCanvas是对RecordingCanvas的封装,负责DisplayListData的大小分配与初始化、SkiaDisplayList的管理和子View的RenderNode更新保存。

    RecordingCanvas直接操作DisplayListData,SkiaRecordingCanvas直接操作SkiaDisplayList。SkiaDisplayList是对DisplayListData的屏蔽封装。

3.渲染流程

    硬件的渲染过程分成三部分:渲染指令列表(SkiaDisplayList)的获取、渲染层级(TreeInfo)的构建、渲染管线(IRenderPipeline)的渲染。

3.1 渲染指令列表的获取

    渲染指令列表的获取过程会阻塞UI线程,在获取完成后会唤醒UI线程。

3.2 渲染层级的构建

    渲染层级的构建过程本质上是从DecorView的RootRenderNode开始向下判断哪些子View的RenderNode发生了变化,并把变化的RenderNode保存到TreeInfo中。

3.3 渲染管线的渲染

    在Android中,渲染管线有两种:SkiaOpenGLPipeline和SkiaVulkanPipeline,底层实现分别对应着OpenGL和Vulkan。

    渲染管线的渲染过程分成两部分:RenderNode的更新渲染和RenderNode的最终渲染。

    渲染管线在渲染过程中会将RenderNode封装成RenderNodeDrawable。RenderNode的更新渲染本质上就是在RenderNode自身的SkCanvas上绘制。RenderNode的最终渲染本质上就是在生产消费模型对应的SkCanvas上绘制。

  • 23
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值