android图形框架之surfaceflinger分析(一)
1. 概念
surfaceflinger作用是接受多个来源的图形显示数据,将它们合成,然后发送到显示设备。比如打开应用,常见的有三层显示,顶部的statusbar底部或者侧面的导航栏以及应用的界面,每个层是单独更新和渲染,这些界面都是有surfaceflinger合成一个framebuffer刷新到display硬件中显示。在显示过程中使用到了bufferqueue,surfaceflinger作为consumer方,比如windwomanager管理的surface作为生产方通过GPU产生的页面,交由surfaceflinger进行合成。
2. 功能
2.1 接收并处理来自GPU RenderThread 图层数据
2.2 合成显示数据发送到display设备
详细时序图(基于高通)
3. 流程
3.1 Surfaceflinger启动流程
3.2 上层framework调用
3.3 activity中surface创建流程
3.4 bufferqueue
负责图形缓冲队列管理
3.4.1 bufferqueue调用流程
App进程通过Binder线程向SurfaceFlinger进程发起allocateBuffer请求,Android硬件渲染环境初始化中用到了allocateBuffer函数,它的作用是预先分配Buffer, 这样可以避免在渲染时分配带来的延时。 allocateBuffer最终会触发BufferQueueProducer去allocateBuffer
流程概述:
① 预分配GraphicBuffer
App通过Binder线程向SurfaceFlinger进程发起allocateBuffer请求,allocateBuffer是预先分配Buffer, 此过程可以减少在渲染时延时。
②通过dequeueBuffer取出buffer
1. 获得一个可用的BufferSlot
2. 通过waitForFreeSlotThenRelock函数
3.从mFreeBuffers(getFreeBufferLocked)或者从mFreeSlots(getFreeSlotLocked)获得一个BufferSlot,
将BufferSlot插入到mActiveBuffers中设置BufferSlot的状态为 DEQUEUE状态变化 FREE -> DEQUEUE
4.检查是否需要重新分配GraphicBuffer由于可能是从1中的mFreeBuffers中获得可用的BufferSlot, 而该BufferSlot已经分配过 GraphicBuffer了,但是此时dequeueBuffer需要的width/height/format/usage与该BufferSlot不对应,此时就需要重新分配一个新的GraphicBuffer
③ requestBuffer
BufferSlot返回给 APP进程中的Surface
④queueBuffer
当新的一帧准备好后(queueBuffer),这时就要通知surfaceflinger 去消费了,这时调用的是 BufferQueueCore::mConsumerListener -> onFrameAvailable. 如下图:
3.4.2 GraphicBuffer底层实现
l. 类图
II. 生产者和消费模型
GraphicBuffer的左膀右臂,GraphicBufferAllocator和GraphicBufferMapper!从Android 8.0开始,Android 操作系统框架在架构方面的一项重大改变,提出了treble 项目。Vendor的实现和Androd的实现分开,Android和HAL,采用HwBinder进行通信,减少Android对HAL的直接依赖。这里的Allocator和Mapper,就是对HAL结合的包装;IAllocator,IMapper的HAL的接口。
如上图所示, BufferQueue模型是一个典型的生产者/消费者模型.
生产者: BufferQueueProducer (APP通过GPU渲染后生产的图形数据)
消费者: BufferQueueConsumer (sufferflinger 通过HWC将图形数据合成后送过显示设备"消费(即显示)" )
3.4.3 底层接口
即android.hardware.graphics.allocator接口,allocator函数流程如下:
根据Buffer Descriptor描述的属性,分配对应的Buffer;count,分配的个数;返回值,stride,Buffer 步长,何为步长?我们知道Buffer都有一个宽度,但是Buffer的内存中分配的时候,都是采用对齐后的大小。多少位对齐,每个硬件平台不一样。比如,我们在一个32对齐的平台上,需要申请一块60x60大小的Buffer。因为要做对齐,所以实际分配的大小为64x60。那么对于这块Buffer,stride就是64。这是因为我们读Buffer的时候,基本都是一行一行的读的,我们要读i行j列,也就是base + i*stride + j的位置。在有的场合下,高也会要求做对齐,那么60x60的Buffer,实际分配的大小是64x64的。buffers这是分配的Buffer的handle了。
去实现Gralloc1的HAL时,allocator只去要实现getCapabilities和上面mDispatch中的gralloc1_function_descriptor_t就可以了。
3.4.4 分配机制
通过IonAlloc采用ion Buffer,负责具体的Buffer处理
简介:ion Buffer是一种内存分配器,是Android 4.0版本开始引入的,用以取代被诟病的PMEM,最显著的特点是它可以被用户空间的进程之间或者内核空间的模块之间进行内存共享,而且这种共享可以是零拷贝的。
分配策略:
- ION_HEAP_TYPE_SYSTEM
- ION_HEAP_TYPE_SYSTEM_CONTIG
- ION_HEAP_TYPE_CARVEOUT
- ION_HEAP_TYPE_CHUNK
- ION_HEAP_TYPE_DMA
- ION_HEAP_TYPE_CUSTOM
3.4.5 IMapper接口
创建一个BufferDescriptor,分配Buffer时,根据Descriptor分配,函数如下:
- limportBuffer
Buffer被冲其他进程或HAL克隆出来时,这个Buffer是RAW状态的Buffer,raw handle是不能直接访问真正的Buffer的,我们需要把它imported
到imported的handle中才能访问。创建imported handle时,需要验证raw handle的有效性,且raw handle需要能多少import创建多个imported handle。在passthrough HALs中,从HAL接收到的handle,可能已经被import到进程中,这个时候要能区分,将其当做raw handle处理,而不
是返回BAD_BUFFER。
- lfreeBuffer
释放Buffer handle,通过importBuffer返回的handle必现通过这个接口释放。importBuffer时申请的所有资源必须一起释放。比如 imported handle如果通过native_handle_create创建的,那么必须调用native_handle_close和native_handle_delete
- llock
将Buffer锁住,用来做制定的处理。多线程可以同事lock,但是不能同时写。超出accessRegion区域的Buffer不能写,超出的区域不受保护。Buffer的地址是指针buffer,是从left-top开始的,即使accessRegion不是left-top描述。
- llockYCbCr
这个lock很相似,只是返回值不一样,这里是YCbCrLayout。除非是Codec配置为flexible-YUV-compatible的颜色格式,要不必现是PixelFormat::YCbCr_*_888格式的
- lunlock
表示CPU访问Buffer已经完成
- lvalidateBufferSize
验证,Buffer能不能被制定的描述信息和步长的访问者访问。
- lgetTransportSize
获取Buffer传输的大小。一个Imported handle是一个raw handle再加上进程本地运行的数据,所以我们可以获取到进程本地的数据。
3.参考
https://www.cnblogs.com/1996swg/p/9790209.html
https://blog.csdn.net/tli600605/article/details/101710446
https://www.cnblogs.com/blogs-of-lxl/p/11272756.html
https://www.jianshu.com/p/dd800800145b
https://www.cnblogs.com/blogs-of-lxl/p/11254738.html