Android渲染
Android渲染整体架构
Android渲染整个过程是一个很典型的生产者消费者模式 App(生产者) <-> BufferQueue(图像缓冲区) <-> SurfaceFlinger(消费者)
渲染流程
- 由app生成渲染数据。app#draw()把绘制指令通过canvas传递给framework层的RenderThread线程
- framework层的RenderThread线程通过surface.dequeue得到缓冲区graphic buffer,然后通过OpenGL完成真正渲染命令,再把缓冲区交还给BufferQueue队列
- SurfaceFlinger(Android中担当消费者角色)。负责从队列中取数据,同时和HAL完成layer的合成工作,最终交给HAL显示(HAL硬件抽象层。负责把图像数据展示到设备显示器上)
生产者
生成渲染数据主要有:MediaPlayer、CameraPreview、NDK(Skia)、OpenGL ES
- MediaPlayer、CameraPreview:直接读取图像源生成图像数据
- NDK(Skia)、OpenGL ES:通过自身绘制产生图像数据
渲染库
OpenGL
- 跨平台的3D图形绘制规范接口,OpenGL ES针对嵌入式设备(手机)做了优化
- 在OpenGL驱动中会把API验证、内存管理、多线程管理等工作帮忙完成,即使API出错,也能保证App正常运行,开发者使用简单,但牺牲了大量性能
- 单线程渲染,资源浪费
Skia
- 图像渲染库(Android、Fullter),支持2D、3D、CPU软硬绘制。
- 2D图形绘制依赖自身完成。3D需依赖硬件,由OpenGL、Vulkan、Metal支持
Vulkan
- 科纳斯组织基于AMD Mantle基础上开发,是用来替换OpenGL的(OpenGL不能充分发挥CPU多核多线程性能优势)
- 支持2D、3D绘制,更轻量级,跨平台
- Vulkan把API验证、内存管理、多线程管理等工作交给开发者负责,一旦API出错,APP就会crash。虽增加了API使用的复杂度和困难度,但大大提升了性能(单在驱动层去掉API验证操作,就将性能提升了9倍)
- 引入Command Buffer概念,每个线程均可往Command Buffer提交渲染数据,可充分发挥CPU多核多线程优势
OpenGL与Vulkan
- Vulkan的优势在于显示控制和多线程功能,可使我们在更少CPU时间内将更多命令推送至GPU,具有更精细的成本控制
- OpenGL的优势在于其提供了更易于使用的硬件访问方式
选择
- 性能
- Vulkan提升的是驱动性能,非渲染流程导致的性能问题,Vulkan不会带来巨大收益
- Vulkan和OpenGL在GPU上的使用没有质的差距,若是GPU导致性能问题(例GPU loading过重),Vulkan也不会带来多大提升
- 若对应用有微小的卡顿或帧率抖动很在意,使用Vulkan可以显示的控制场景渲染期间何时发生了耗时操作,比OpenGL通过推断的方式管理状态和资源更有优势
- 若希望充分发挥CPU多核多线程能力,则选Vulkan
- Vulkan在离屏渲染上也很有优势
- 复杂度
- Vulkan复杂的API对开发者是个挑战,由开发者书写代码量大(例三角形绘制需上千行代码),OpenGL降低了代码复杂性和维护负担,提供了便于使用的API
- Vulkan驱动层把API验证、资源管理等交给开发者,需额外考虑内存管理和线程同步,否则会crash或花屏。而OpenGL驱动层已实现,开发者无需额外处理
- Vulkan虽支持跨平台,但因其Vulkan extension是平台相关的,所以代码耦合性会很高
CPU与GPU
CPU与GPU是除屏幕外,UI渲染的核心硬件
CPU
- 中央处理器
- 计算机运算与控制的核心,信息处理和程序运行的最终执行单元
GPU
- 图形处理器
- 专门针对图像运算的处理器,是显卡的核心部件
- UI绘制到屏幕前会经过栅格化(Rasterization)操作,栅格化是绘制Button、Path、Bitmap等显示的基本操作,其将UI组件拆分到显示器的不同像素上显示,其非常耗时,GPU的引入是为了加快栅格化
软硬绘制
从上图可以看出,硬绘制是通过底层代码实现,使用GPU图形计算功能替代CPU,由GPU完成绘制任务
硬件加速本质上是使用GPU代替CPU完成Graphic Buffer绘制工作,以提高绘制性能。Android 4.0开始支持
软绘制虽使用Skia,但不代表其不支持硬件加速,Skia在Android 8.0开始支持,Skia在Android 9.0默认硬件加速。原理:通过 copybit 模块调用 OpenGL 或 Skia 实现
BufferQueue图像缓冲区
Android中OpenGL、Skia、Vulkan将绘制数据存放至图像缓冲区,SurfaceFlinger从中取出进行加工及合成,最终显示至屏幕
黄油计划
2012年Google在I/O大会宣布Project Butter,并在Android 4.1开始启动该机制
VSYNC信号
- 全称
Vertical Synchronization,是Project Butter的核心。 - CPU/GPU会根据
VSYNC信号同步处理数据,可使CPU/GPU有完整的16ms时间处理数据,减少jank(卡顿)
三缓冲机制
Android 4.0之前,采用双缓冲机制,绘制和显示器拥有各自的buffer。GPU将完成的一帧图像数据写入到Back Buffer,显示器使用Frame Buffer,屏幕刷新时Frame Buffer不变,当Back Buffer准备好后,交换数据。但在复杂界面中,CPU/GPU处理时间超过16ms就会出现jank双缓冲机制中会在VSYNC时交换数据,但CPU与GPU绘制是不确定的。上图分析
- 显示器显示第0帧数据,此时CPU和GPU渲染第一帧画面,且在下一帧前完成显示
- 因渲染及时,显示器显示完第0帧后(即第一个VSYNC后),交换
buffer数据,正常显示第一帧 - 接着第二帧开始处理,但第二个
VSYNC即将到来时才开始处理,因此在第二个VSYNC到来时,数据未处理完,缓存未交换,显示器显示的还是第1帧内容。即发生Jank(丢帧) - 第二帧准备就绪后并不会立即显示,而是要等待下一个
VSYNC,然后交换缓存数据并显示 - 如上屏幕会多显示一次第一帧。原因:CPU、GPU计算超时,未能在
VSYNC信号到来之前完成任务
- 三缓冲机制就是在双缓冲机制的基础上增加了一个
Graphic Buffer缓冲区,有效利用等待VSYNC的时间,从而减少jank。缺点是多占用了一块内存
- 第一个jank不可避免,但第二个16ms时间段,CPU/GPU使用第三个Buffer完成3帧计算,虽会多显示一次1帧,但后续比较顺畅,有效避免jank
- 第3段中,1帧计算完成,但在第4个VSYNC来时才显示,若是双缓存,第3个VSYNC即可显示
- Buffer正常两个,出现jank后3个足以