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

相关推荐
SharpCJ6 小时前
Android 开发者为什么必须掌握 AI 能力?端侧视角下的技术变革
android·ai·aigc
_李小白7 小时前
【OSG学习笔记】Day 38: TextureVisitor(纹理访问器)
android·笔记·学习
JJay.7 小时前
Kotlin 高阶函数学习指南
android·开发语言·kotlin
jinanwuhuaguo7 小时前
截止到4月8日,OpenClaw 2026年4月更新深度解读剖析:从“能力回归”到“信任内建”的范式跃迁
android·开发语言·人工智能·深度学习·kotlin
JJay.8 小时前
Android Kotlin 协程使用指南
android·开发语言·kotlin
BLUcoding8 小时前
Android 布局介绍
android
summerkissyou19878 小时前
android-蓝牙-状态和协议值总结及监听例子
android·蓝牙
徒 花8 小时前
数据库知识复习05
android·数据库
提子拌饭13310 小时前
番茄时间管理:鸿蒙Flutter 实现的高效时间管理工具
android·flutter·华为·架构·开源·harmonyos·鸿蒙
4311媒体网11 小时前
帝国CMS二次开发实战:精准实现“最新资讯”标识与高亮判断
android