Android GPU的RenderThread Texture upload上传Bitmap优化prepareToDraw

Android GPU的RenderThread Texture upload上传Bitmap优化prepareToDraw

基本原理

View树中所有元素的材料最终会封装到DisplayList对象中(后期版本有用RenderNode对DisplayList又做了一层封装,实现了更好的性能),然后发送出去,这样第一阶段就完成。

有一个重要的问题:这个阶段怎么处理Bitmap?

会将Bitmap复制到下一个阶段(复制到GPU的内存中)。 现在大多数设备使用了GPU硬件加速,而GPU在渲染来自Bitmap的数据时只能读取GPU内存中的数据, 所以需要赋值Bitmap到GPU内存,这个阶段对应的名称叫Sync & upload。但是,硬件加速并不支持所有Canvas API,如果自定义View使用了不支持硬件加速的Canvas API(参考Android硬件加速文档)。

这时可能会有问题:如果Bitmap很多或者单个Bitmap尺寸很大,这个过程可能会时间比较久,那有什么办法吗?

预上传: Bitmap.prepareToDraw()(from Android 7.0 - Nougat)

使用Hardware-Only Bitmap(from Android 8.0 - Oreo)

从Android 8.0 开始,支持了Hardware-Only Bitmap类型,这种类型的Bitmap的数据只存放在GPU内存中,这样在 Sync & upload 阶段就不需要upload这个Bitmap了。使用很简单,只需要将Options.inPreferredConfig赋值为Bitmap.Config.HARDWARE即可。

这种方式能实现特定场景的极致性能,但这种Bitmap的某些操作是受限的(毕竟数据存储只存储在GPU内存中),可以查看Glide的总结文档:https://bumptech.github.io/glide/doc/hardwarebitmaps.html

在Android3.0~Android7.0,Bitmap内存放在Java堆中,而android系统中每个进程的Java堆是有严格限制的,处理不好这些Bitmap内存,容易导致频繁GC,甚至触发Java堆的OutOfMemoryError。从Android8.0开始,bitmap的像素数据放入了native内存,于是Java Heap的内存问题暂时缓解了。

接下来就要把Bimap画出来,画出来的过程就是把前面的Bimap转化成一堆像素数据的过程,也叫栅格化,那这个活儿谁来干呢?

只有两个:

CPU: 软件绘制,使用Skia方案实现,绘制慢。

GPU: 硬件加速绘制,使用OpenGL ES或Vulkan方案实现,绘制快很多。

大部分情况下,都是GPU来干这个活儿,因为GPU真的特别快。

画在哪里

至于画在哪里,可以理解为一个缓冲(Buffer)。到此,已经画(绘制)完了图像内容,把这个内容发送出去,任务完成。

显示到屏幕

BufferQueue

Android图形架构中,使用生产者消费者模型来处理图像数据,其中的图像缓冲队列叫BufferQueue, 队列中的元素叫Graphic Buffer,队列有生产者也有消费者;每个应用通常会对应一个Surface,一个Surface对应着一个缓冲队列,每个队列中Graphic Buffer的数量不超过3个(经典模型), 上面绘制的Bitmap数据最终会放入一个Graphic Buffer,应用自身就是队列的生产者。

每个Graphic Buffer本身体积很大,在从生产者到消费者的传递过程中不会进行复制操作,用匿名共享内存的方式,通过句柄来跨进程传递。

性能优化

Android 会将Bitmap显示为 OpenGL 纹理,并且当Bitmap第一次显示在帧中时,它会上传到 GPU。在 trace 中看到此操作显示为Texture upload(id)"宽 x 高"。这可能需要几毫秒的时间,但必须使用 GPU 显示图片。

如果这些操作用时较长,首先检查跟踪Bitmap的宽度和高度数据。请确保显示的Bitmap不会明显大于其在真正显示区域的尺寸,否则会浪费上传时间和内存。

Texture upload上传纹理是一个极为耗时的过程,在1080×1920的屏幕尺寸下传一张全屏的texture耗时不少。这样的话SurfaceFlinger就不可能在正常周期下流畅绘制。因此,Android采用native buffer,将graphic buffer直接作为纹理(direct texture)进行操作。

在 Android 7.0 中,Bitmap加载代码(通常由库完成)可以调用 prepareToDraw(),以便在需要用到它之前便提前触发上传。这样,上传upload操作会在 RenderThread 处于空闲状态时提前进行。只要获得Bitmap,就可以在解码之后或将Bitmap绑定到View时执行此操作。理想情况下,Bitmap加载库会自动执行此操作,但如果要自行管理,或者想要确保在更高版本的设备上不会触发主动上传,则可以在自己的代码中调用 prepareToDraw()。

Google开发人员对Bitmap的prepareToDraw优化建议( https://github.com/facebook/fresco/issues/1756 ):

ChrisCraik

opened on May 9, 2017

Description

Hey, I work on UI Toolkit Graphics in Android. Wanted to pass on an optimization you can enable for displaying Bitmaps.

Android displays Bitmaps as OpenGL textures, and the first time a Bitmap is displayed in a frame, it's uploaded to the GPU. That can take several milliseconds, but it's necessary to display the image with the GPU.

In Android N, we added behavior to Bitmap#prepareToDraw() to send an async message to RenderThread to pre-upload. RenderThread uploads these to the GPU when it expects to be idle between frames, instead of while drawing the first frame that uses the Bitmap. If you can do this after decoding, you can make it much less likely that frames are dropped due to uploading.

Reproduction

In Systrace, you can see the problem when any Bitmap is displayed by looking for sections labeled 'Upload <w>x<h> Texture'. If those are happening in 'DrawFrame' on the RenderThread, inside 'syncFrameState,' they haven't been pre-uploaded, and are on the critical path for the frame.

Solution

Call Bitmap#prepareToDraw as early as possible when you think a Bitmap will be displayed soon. The most aggressive way to do this is any time you decode an image (especially any immutable image).

The drawback is that if you call it on a bitmap before you modify it, or if it doesn't get drawn, you'll be wasting the time spent uploading. If you're reasonably sure the Bitmap in question will be displayed, it's still a great way to prefetch the upload work on RenderThread.

Additional Information

Behavior added in Android N:

https://android.googlesource.com/platform/frameworks/base/+/4387190d8ec9fe4e953fcfeb093a644b82cf85ed

For reference, Glide implemented this here: bumptech/glide@dce9550

It's safe to call on older versions, so you don't need a version check. The method existed prior to N, but was a noop for normal Bitmaps.

相关:

https://developer.android.com/topic/performance/vitals/render?hl=zh-cn

https://blog.csdn.net/zhangphil/article/details/145608757

https://bumptech.github.io/glide/doc/hardwarebitmaps.html

相关推荐
柿蒂4 小时前
聊聊SliverPersistentHeader优先消费滑动的设计
android·flutter
假装多好1236 小时前
android三方调试几个常用命令
android·1024程序员节·三方,gms
侧耳4296 小时前
android11禁止安装apk
android·java·1024程序员节
JohnnyDeng947 小时前
ArkTs-Android 与 ArkTS (HarmonyOS) 存储目录全面对比
android·harmonyos·arkts·1024程序员节
2501_915918417 小时前
iOS 26 查看电池容量与健康状态 多工具组合的工程实践
android·ios·小程序·https·uni-app·iphone·webview
limingade7 小时前
手机摄像头如何识别体检的色盲检查图的数字和图案(下)
android·1024程序员节·色盲检查图·手机摄像头识别色盲图案·android识别色盲检测卡·色盲色弱检测卡
文火冰糖的硅基工坊8 小时前
[嵌入式系统-150]:智能机器人(具身智能)内部的嵌入式系统以及各自的功能、硬件架构、操作系统、软件架构
android·linux·算法·ubuntu·机器人·硬件架构
2501_915909069 小时前
iOS 架构设计全解析 从MVC到MVVM与使用 开心上架 跨平台发布 免Mac
android·ios·小程序·https·uni-app·iphone·webview
明道源码10 小时前
Android Studio 创建 Android 模拟器
android·ide·android studio