Android display架构分析五-Display接口介绍
1、User Space display接口
在Android平台下,应用程序面对的显示部分的接口就是HAL,参考copybit.cpp (qcom\diaplay\libcopybit),具体接口如下介绍:
open_copybit 初始化相关变量,并调用open(“/dev/graphics/fb0″, O_RDWR, 0);打开fb设备。
set_parameter_copybit 设置各种操作参数,如rotate、alpha、dither等。
stretch_copybit Copy一块数据(Rectangle)到显存,然后并命令msm_fb进行显示。
close_copybit 调用close(ctx->mFD);关闭fb设备。
Note:另外,应用程序在使用上面接口之前,需要调用mapFrameBuffer接口(EGLDisplaySurface.cpp),其功能如下:
1、 初始化显示相关参数,并设置到底层。
2、 映射出显存的虚拟地址。
2、Kernel display接口
Kernel部分显示的接口全部都在fbmem.c中,这里详细介绍一下:
fb_open 打开Linux下fb设备。
fb_read/fb_write 读写显存中的数据
fb_ioctl 对显示设备的命令操作。如get或set一些显示参数、通知底层进行刷屏等。
在典型应用中,画屏的一般步骤如下:
1. 打开/dev/fb设备文件。
2. 用ioctrl操作取得当前显示屏幕的参数,如屏幕分辨率,每个像素点的比特数。根据屏幕参数可计算屏幕缓冲区的大小。
3. 将屏幕缓冲区映射到用户空间。
4. 映射后就可以直接读写屏幕缓冲区,进行绘图和图片显示了。
典型程序段如下:
- #include <linux/fb.h>
- int main()
- {
- int fbfd = 0;
- struct fb_var_screeninfo vinfo;
- struct fb_fix_screeninfo finfo;
- long int screensize = 0;
- /*打开设备文件*/
- fbfd = open("/dev/fb0", O_RDWR);
- /*取得屏幕相关参数*/
- ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo);
- ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo); //该函数最后会将参数传递给finfo、vinfo
- /*计算屏幕缓冲区大小*/
- screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;
- /*映射屏幕缓冲区到用户地址空间*/
- fbp=(char*)mmap(0,screensize,PROT_READ|PROT_WRITE,MAP_SHARED, fbfd, 0);
- /*下面可通过fbp指针读写缓冲区*/
- ...
- }
3典型应用flow分析
在不同应用程序中,上层的调用会有所不同,比如Andriod下会选择应用程序跳过Linux fb操作层,直接操作显卡驱动层,称之为BLT accelerator。
下面看一下Android平台下画屏的操作流程。
1、 通过mapFrameBuffer直接把用户空间的数据映射到显存中。
2、 调用HAL中的stretch函数直接命令MSM设备提取显存数据然后送入MDP PPP进行处理并经MDDI接口送到外围LCD组件。
具体的函数调用流程如下:
- copybit_open(); //打开BlitEngine,同时也打开fb设备
- mapFrameBuffer(); //设置显示参数,同时得到显存虚拟地址
- copybit->stretch(copybit, &dst, &src, &sdrect, &sdrect, &it); //通知底层去刷屏
- stretch_copybit
- 调用:status = msm_copybit(ctx, &list);
- ->msm_copybit
- 调用int err = ioctl(dev->mFD, MSMFB_BLIT, (struct mdp_blit_req_list const*)list);
- //此处应该没有调用到:fb_ioctl(),直接跳到msm_fb_ioctl
- ->msm_fb_ioctl(MSMFB_BLIT) //此处将调用msm_fb.c文件中msm_fb_ioctl()函数中的switch结构,跳入MSMFB_BLIT分支调用:
- ->case MSMFB_BLIT:
- ret = msmfb_blit(info, argp);
- -> int ret = mdp_blit(info, &(req_list[i]));
- -> mdp_ppp_blit
- -> mdp_start_ppp
- ->MDP&MDDI HW operation
本部分介绍的完全是用户空间显示部分的架构,与kernel并没有直接的联系,主要是JNI以下到HAL以上的部分。
1、Surface manager(surface flinger)简介
Surface manager是用户空间中framework下libraries中负责显示相关的一个模块。如下:
当系统同时执行多个应用程序时,Surface Manager会负责管理显示与存取操作间的互动,另外也负责将2D绘图与3D绘图进行显示上的合成。
surface manager 可以准备一块 surface(可以看作一个layer),把 surface 的 fd (一块内存) 传给一个 app,让 app 可以在上面作画。 典型应用如下:
2、Surface manager架构分析
Android中的图形系统采用Client/Server架构,如下:
Client端:应用程序相关部分。代码分为两部分,一部分是由Java提供的供应用使用的api,另一部分则是由c++写成的底层实现。
Server端:即SurfaceFlinger,负责合成并送入buffer显示。其主要由c++代码编写而成。
Client和Server之前通过Binder的IPC方式进行通信,总体结构图如下:
如上图所示,Surface的client部分其实是提供给各应用程序进行画图操作的一个桥梁,该桥梁通过binder通向server端的Surfaceflinger,Surfaceflinger负责合成各个surface,然后把buffer传送到framebuffer端进行底层显示。其中每个surface对应2个buffer,一个front buffer, 一个back buffer,更新时,数据更新在back buffer上,需要显示时,则将back buffer和front buffer互换。
下一部分我们重点研究一下Surfaceflinger。
Android display架构分析七-Surfaceflinger process流程分析
根据前面的介绍,surfaceflinger作为一个server process,上层的应用程序(作为client)通过Binder方式与其进行通信。Surfaceflinger作为一个thread,这里把它分为3个部分,如下:1、 Thread本身处理部分,包括初始化以及thread loop。
2、 Binder部分,负责接收上层应用的各个设置和命令,并反馈状态标志给上层。
3、 与底层的交互,负责调用底层接口(HAL)。
a、 Binder接收到应用程序的命令(如创建surface、设置参数等),传递给flinger。
b、 Flinger完成对应命令后将相关结果状态反馈给上层。
c、 在处理上层命令过程中,根据需要设置event(主要和显示有关),通知Thread Loop进行处理。
d、 Flinger根据上层命令通知底层进行处理(主要是设置一些参数,Layer、position等)
e、 Thread Loop中进行surface的合成并通知底层进行显示(Post buffer)。
f、 DisplayHardware层根据flinger命令调用HAL进行HW的操作。
下面来具体分析一些SurfaceFlinger中重要的处理函数以及surface、Layer的属性
1)、readToRun
SurfaceFlinger thread的初始化函数,主要任务是分配内存和设置底层接口(EGL&HAL)。
- status_t SurfaceFlinger::readyToRun()
- {
- …
- mServerHeap = new MemoryDealer(4096, MemoryDealer::READ_ONLY); //为IPC分配共享内存
- …
- mSurfaceHeapManager = new SurfaceHeapManager(this, 8 << 20); //为flinger分配heap,大小为8M,存放具体的显示数据
- {
- // initialize the main display
- GraphicPlane& plane(graphicPlane(dpy));
- DisplayHardware* const hw = new DisplayHardware(this, dpy);
- plane.setDisplayHardware(hw); //保存显示接口
- }
- //获取显示相关参数
- const GraphicPlane& plane(graphicPlane(dpy));
- const DisplayHardware& hw = plane.displayHardware();
- const uint32_t w = hw.getWidth();
- const uint32_t h = hw.getHeight();
- const uint32_t f = hw.getFormat();
- …
- // Initialize OpenGL|ES
- glActiveTexture(GL_TEXTURE0);
- glBindTexture(GL_TEXTURE_2D, 0);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- …
- }
此部分可以参考文章:SurfaceFlinger启动过程分析 Android系统Surface机制的SurfaceFlinger服务的启动过程分析
2)、ThreadLoop
Surfaceflinger的loop函数,主要是等待其他接口发送的event,进行显示数据的合成以及显示
- bool SurfaceFlinger::threadLoop()
- {
- waitForEvent(); //等待其他接口的signal event
- …
- handlePageFlip(); //处理翻页机制
- const DisplayHardware& hw(graphicPlane(0).displayHardware());
- if (LIKELY(hw.canDraw()))
- {
- // repaint the framebuffer (if needed)
- handleRepaint(); //合并所有layer并填充到buffer中去
- …
- postFramebuffer(); //互换front buffer和back buffer,调用EGL接口进行显示
- }
- …
- }
在最新的4.1的代码中,threadLoop只调用了waitForEvent,其他部分的实现均放在onMessageReceived()函数中实现【SurfaceFlinger.cpp】
谷歌在Android native层实现的一个异步消息机制,在这个机制中几乎不存在同步锁,所有的处理都是异步的,将变量封装到一个消息AMessage结构体中,然后放到队列中去,后台专门有一个线程会从这个队列中取出消息然后执行,执行函数就是onMessageReceived,这个函数中会有很多分支,用于处理不同的消息;在很多类中都会有各种消息post出来,而后台的异步消息处理线程又是怎么知道发送给哪个类的onMessageReceived函数处理呢,要搞懂这个问题,就需要把谷歌实现的这个异步消息处理框架搞明白,参考文章:http://blog.sina.com.cn/s/blog_645b74b90101cx69.html
3)、createSurface
提供给应用程序的主要接口,该接口可以创建一个surface,底层会根据参数创建layer以及分配内存,surface相关参数会反馈给上层
- sp<ISurface> SurfaceFlinger::createSurface(ClientID clientId, int pid,
- ISurfaceFlingerClient::surface_data_t* params,
- DisplayID d, uint32_t w, uint32_t h, PixelFormat format,
- uint32_t flags)
- {
- …
- int32_t id = c->generateId(pid);
- if (uint32_t(id) >= NUM_LAYERS_MAX) //NUM_LAYERS_MAX=31
- {
- LOGE("createSurface() failed, generateId = %d", id);
- return
- }
- …
- layer = createNormalSurfaceLocked(c, d, id, w, h, format, flags); //创建layer,根据参数(宽高格式)分配内存(共2个buffer:front/back buffer)
- if (layer)
- {
- setTransactionFlags(eTransactionNeeded);
- surfaceHandle = layer->getSurface(); //创建surface
- if (surfaceHandle != 0)
- surfaceHandle->getSurfaceData(params); //创建的surface参数反馈给应用层
- }
- }
4)、setClientState
处理上层的各个命令,并根据flag设置event通知Threadloop进行处理
- status_t SurfaceFlinger::setClientState(
- ClientID cid,
- int32_t count,
- const layer_state_t* states)
- {
- Mutex::Autolock _l(mStateLock);
- uint32_t flags = 0;
- cid <<= 16;
- for (int i=0 ; i<count ; i++) //检测所有存在layer的状态标志
- {
- const layer_state_t& s = states[i];
- LayerBaseClient* layer = getLayerUser_l(s.surface | cid);
- if (layer)
- {
- const uint32_t what = s.what; // 检测应用层是否设置各个标志,如果有则通知底层完成对应操作,并通知ThreadLoop做对应的处理
- if (what & eDestroyed) //删除该层Layer
- {
- if (removeLayer_l(layer) == NO_ERROR)
- {
- flags |= eTransactionNeeded;
- continue;
- }
- }
- if (what & ePositionChanged) //显示位置变化
- {
- if (layer->setPosition(s.x, s.y))
- flags |= eTraversalNeeded;
- }
- if (what & eLayerChanged) //Layer改变
- {
- if (layer->setLayer(s.z))
- {
- mCurrentState.layersSortedByZ.reorder(
- layer, &Layer::compareCurrentStateZ);
- flags |= eTransactionNeeded|eTraversalNeeded;
- }
- }
- if (what & eSizeChanged)
- {
- if (layer->setSize(s.w, s.h)) //设置宽高变化
- flags |= eTraversalNeeded;
- }
- if (what & eAlphaChanged) { //设置Alpha效果
- if (layer->setAlpha(uint8_t(255.0f*s.alpha+0.5f)))
- flags |= eTraversalNeeded;
- }
- if (what & eMatrixChanged) {//矩阵参数变化
- if (layer->setMatrix(s.matrix))
- flags |= eTraversalNeeded;
- }
- if (what & eTransparentRegionChanged) { //显示区域变化
- if (layer->setTransparentRegionHint(s.transparentRegion))
- flags |= eTraversalNeeded;
- }
- if (what & eVisibilityChanged) {//是否显示
- if (layer->setFlags(s.flags, s.mask))
- flags |= eTraversalNeeded;
- }
- }
- }
- if (flags)
- { setTransactionFlags(flags); //通过signal通知ThreadLoop
- }
- return NO_ERROR;
- }
5)、composeSurfaces ,在 handleRepaint()中调用到
该接口在Threadloop中被调用,负责将所有存在的surface进行合并,OpenGl模块负责这个部分。
6)、postFramebuffer
该接口在Threadloop中被调用,负责将合成好的数据(存于back buffer中)推入在front buffer中,然后调用HAL接口命令底层显示。
7)、从3中可知,上层每创建一个surface的时候,底层都会同时创建一个layer,下面看一下surface及layer的相关属性。
Note:code中相关结构体太大,就不全部罗列出来了
A、Surface相关属性(详细参考文件surface.h)
a1:SurfaceID: 根据此ID把相关surface和layer对应起来
a2:SurfaceInfo 包括宽高格式等信息
a3:2个buffer指针、buffer索引等信息
B、Layer相关属性(详细参考文件layer.h/layerbase.h/layerbitmap.h)
包括Layer的ID、宽高、位置、layer、alpha指、前后buffer地址及索引、layer的状态信息(如eFlipRequested、eBusy、eLocked等)
Android display架构分析八-Display 开发的经验分享
1添加新的Display Driver的工作内容
参考上面linux下fb设备的软件架构,可以知道,要加入一个新的MDDI 接口的LCM,Driver的工作就是要提供自己的mddi_xxxx.c(在这次porting的过程中,为了节省时间,我们直接修改了mddi_toshiba.c),并且完成和这个lcd相关的HWr的初始化。主要的工作包括:
A、初始化和LCD / LCD背光相关的IO以及电源;
B、编写初始化函数 。主要是初始化LCD控制器,这个一般LCD厂商会提供;然后分配显存,这个高通release过来的code已经包含这个动作了,最后是初始化一个fb_info的结构体,在这里主要是把LCD的一些信息登记进来。
C、把LCD的设备以及驱动注册到系统中去。(这里因为是替换现有的驱动,所以相关修改的部分不多。)
上述B、C部分代码请参考kernel\drivers\video\msm\mddi_toshiba.c。
2Display Driver开发过程
1.2.1配置Power和IO
更改一些GPIO的配置以及一些电源的电平配置;然后通过实际测量,确保一下信号正常:
A、供给LCD以及MDDI Bridge的电源;
B、MDDI Bridge以及LCD reset信号;
C、控制背光IC的GPIO工作正常(背光不打开,无法调试LCD)。
1.2.2Porting LCD初始化序列
LCD init的code以及外围MDDI Bridge的初始化code,都可以之前Boston Windows Mobile系统的code base中获得;把这部分code移植到mddi_Toshiba.c中,并更改相应的图像格式、分辨率等配置,编译通过。LCD初始化部分就算基本完成。
1.2.3LCD初始化过程的调试
由于硬件在之前Boston load是可以工作的,可以认为硬件连接等没有问题,所以只需关注软件部分就行。
Display部分软件调试过程如下:
A、 开机后,量一下GPIO是否为code中配置预期的状态(可确保code中的
GPIO接口工作正常);
B、 量一下各个电源是否都处于Code中定义的电平值。这些都OK后,背光
是会亮的(背光的控制比较简单,一个GPIO即可);
C、 这个时候如果LCD以及MDDI Bridge有被正常初始化的话,屏幕上是会
看出来的。反之,如果屏幕没有显示,需要用JTAG跟一下mddi_Toshiba.c中的初始化函数是否在开机的时候有被调用过。
目前版本中,是根据外围MDDI Bridge中读到的的厂商号来决定加载哪个驱动模块的。在本次调试中,bootloader中可以正确读到厂商号,所以bootloader中对于LCD的初始化是有做的,所以屏幕看到的状态就是LCD初始化后的样子(花屏)。但Kernel起来后,并没有其他显示,用JTAG跟了后发现,Kernel中MODULE INIT中读不到正确的厂商号,所以说后面的driver没有被加载。接着发现如果在bootloader中如果不做MDDI Bridge的初始化,的话后面的MODULE INIT就可正常运行,该问题目前还没有澄清(现在暂时先把bootloader中的init disable掉)。
1.2.4LCD的调整
初始化正常后,屏幕会显示UI的相关画面,但明显颜色、位置都不对。
这个可能是数据类型配置不对导致的,即MDP输出的类型、MDDI配置的类型以、LCD接收的类型不匹配导致,也有可能是RGB的顺序不对导致(可配置成BGR)。经过调试后,把MDP端输出的格式配置成RGB565,同时外围MDDI Bridge以及LCD的input格式也配置成RGB565,这时显示色彩正常了。
如果位置或者方向不对,比如说上下或是左右颠倒,可以更改LCD的配置中的扫描方向即可。
1.2.5其他
后续发现一个问题,播放video的时候颜色都是黑白的。
这个问题很容易让人误解,按照正常的理解,video decode出来的数据为YCbCr,Y为亮度信号,CbCr为色差信号,如果只有Y信号的话颜色应该就是黑白的。所以有2个怀疑点,一个是decode出来的数据有误,另一个是MDDI Bridge误把输入的YcbCr信号当作RGB信号进行出来,这个也是有可能的。但很快第二个怀疑点被排除了(因为单更改MDDI input格式后还是不能解决问题)。
后来又详细的看了显示部分的代码,并用JTAG追踪video播放的时候用的显示接口,发现目前所有的显示接口输出的格式都是RGB格式,也就是说在通过MDP之前YcbCr已经被转化过;而MDP里的转换功能并没有使用,MDP只是被当作一个DMA完成数据的直接传输,文档中叫做Bypasse。
YcbCr到RGB的转换是由Android的lib来完成。发了个SR给高通,高通的回复也确认了,在6.3.50中,Android上层缺少这个lib(copybit.default.so),6.3.60之后的版本经解决了这个问题。
显示部分的几个问题这几天通过实际测试澄清了一下,主要是下图中各个模块的使用状况以及HAL层几个模块的调用流程。以问题的方式描述如下:
1、Ap是怎么进行显示的?
Surfaceflinger负责所有上层的显示处理,对于AP(2D或是3D的应用程序)而言,只要到surfaceflinger中创建surface,设置好参数,接下来都是统一交给surfaceflinger进行处理
2、Surface是怎么管理多个surface的?
不管有多少个surface,最终送到显示部分的只能是屏幕大小数据,surfaceflinger中利用MDP或是GPU进行多个surface的合成处理,普通的合成MDP就可完成,但如果是复杂的比如3D的应用等就必须使用GPU,最终合成的好数据会被送到framebuffer中。
3、Framebuffer是什么?
Framebuffer是Linux中为显示数据分配的一块显存(fb设备中),通常大小是一整个屏幕数据的两倍,对于上层AP而言,只需要将要显示的数据丢到framebuffer中就OK了,但此时显示数据并未真正的被送到LCD上,而是暂存在framebuffer中而已。
4、上层是通过什么方式将显示内容送到framebuffer的?
有2个方式(二选一,不会同时在运行):
A、普通的显示,使用copybit(MDP)(未使用GPU)
Surfaceflinger通过copybit将要显示的数据送到framebuffer。
Note:copybit可以看做是MDP PPP的接口,它提供了MDP的功能,如多个layer合成,scale、rotate等。
其接口在:android/hardware/msm7k/libcopybit/copybit.cpp
B、使用GPU(即使用图中的Graphics driver)
当进行复杂的显示处理时,比如3D的应用,GPU把处理好的数据直接丢到framebuffer中,和MDP没有任何关系
5、Framebuffer中的数据是如何被送到LCD显示的?
图中的Gralloc完成的。
Gralloc有2个功能:
一个是和copybit相同的,里面有MDP PPP的接口(目前没有使用)
另一个则是刷屏(整屏刷)的接口,即将framebuffer中的数据送到lcd上,调用的是MDP DMA的接口
这部分的code在android/hardware/msm7k/libgralloc-qsd8k目录下,之前没有留意,以为没有使用。现在可以看出开机初始化后就创建了disp_loop thread,里面的操作就是调用系统接口ioctl(m->framebuffer->fd, FBIOPUT_VSCREENINFO, &m->info)将数据送到lcd
Note:送数据的时候是2个buffer切换的
另外,上层surfaceflinger也是通过Gralloc中的接口获知屏幕的大小,调用接口为ioctl(fd, FBIOGET_VSCREENINFO, &info),info中的屏幕宽高对应的就是底层driver设置的宽高值
6、OpenGL是什么?
它是一个图像处理引擎,当需要一些复杂的显示(2D/3D)操作时会用到它。它分为SW方案和HW方案,
软件方案就是图中的libagl.so,对应到目前项目中是libGLES_android.so,它可以完成简单的2D(文字,icon等)处理,通过trace看目前大部分显示操作都是它来完成的。
Note:它是软件方案,处理好的数据是通过copybit送到framebuffer的,而不是GPU。其接口部分参考:android/frameworks/base/opengl/libagl
HW方案就是图中的Graphics driver,它通过使用GPU硬件来完成图像处理,处理后的数据直接送到framebuffer中。其接口部分参考android/frameworks/base/opengl/libs(有几个版本)
7、OpenGL在项目中是如何配置的?
在android/vendor/qcom/msm7627_ffa目录下有一个egl.cfg文件,里面指定了当前版本中的OpenGL信息,目前如下:
0 0 android 第一行代表该codebase支持SW 方案的OpenGL,是android default的
0 1 adreno200 第二行代表该codebase也支持HW方案的OpenGL,是高通的adreno引擎
如果该cfg文件为空,则只支持default的SW方案。
如果2个方案都在,上层将根据实际应用自行选择使用其一。
该部分请参考:android/frameworks/base/opengl/libs/EGL/loader.cpp
上一篇:Android display架构分析-SW架构分析(1-4)