1. 程式人生 > >Suface & Suface Flinger知識

Suface & Suface Flinger知識


android surfaceflinger研究----顯示系統

2011-12-04 18:46 4844人閱讀 評論(11) 收藏 舉報

   這周抽空研究了一下SurfaceFlinger,發現真正複雜的並不是SurfaceFlinger本身,而是androiddisplay顯示系統,網上關於這部分的介紹有不少,本不打算寫的,但是發現還是記錄一下研究程式碼的過程比較好,一是能夠幫助自己理清思路,另一個原因就是以後當這塊內容忘記的時候,能快速的通過這個記錄撿起來。

    .  android

顯示系統的建立

    我們看SurfaceFlinger的定義就知道,它其實是一個Thread,因此SurfaceFlinger的初始化工作就理所當然的放在了SurfaceFlinger執行緒中,詳見readyToRun()@SurfaceFlinger.cpp

   SurfaceFlinger對於顯示的管理是通過一個或多個GraphicPlane物件(目前android只實現了一個)來管理的,

@SurfaceFlinger.h

[cpp] view plaincopy

1. GraphicPlane                mGraphicPlanes[1];  

    其實,GraphicPlane類只是一個wrapper層,目的是當android支援多個顯示系統時,通過該類來管裡各自的圖形系統,顯示系統真正的初始化工作是通過DisplayHardware類來初始化底層圖形系統的管理與顯示的。真正的圖形顯示系統的初始化在init()@DisplayHardware.cpp

    目前,android支援一個圖形系統,這個圖形系統是全域性的,surfaceflinger可以訪問,其他不通過surfaceflinger進行圖形處理的application也可以對其進行操作。

    1. FrameBuffer

的建立

   framebuffer,確切的是說是linux下的framebuffer,,它是linux圖形顯示系統中一個與圖形硬體無關的抽象層,user完全不用考慮我們的硬體裝置,而僅僅使用framebuffer就可以實現對螢幕的操作。

   androidframebuffer並沒有被SurfaceFlinger直接使用,而是在framebuffer外做了一層包裝,這個包裝就是FramebufferNativeWindow,我們來看一下FramebufferNativeWindow的建立過程。

  我們的framebuffer是由一個裝置符fbDev來表示的,它是FramebufferNativeWindow的一個成員,我們來分析一下對fbDev的處理過程。

    1.1. fbDev裝置符

    1.1.1 gralloc library

   在這之前,先介紹一下gralloc library,它的形態如grallocBOARDPLATFORM.so BOARDPLATFORM可以從屬性ro.board.platform中獲得,這篇文章中我們以Qualcomm msmx7x30為例,也就是gralloc.msm7x30.so中,它的源路徑在hardware/msm7k/libgralloc-qsd8k

   framebuffer的初始化需要通過HAL gralloc.msm7x30.so 來完成與底層硬體驅動的適配,但是gralloclibrary並不是平臺無關的,不同的vendor可能會實現自己的gralloc library,因此為了保證在建立framebuffer時能夠平臺無關,android只能是動態的判斷並使用當前的gralloc libraryandroid通過從gralloc library中再抽象出一個hw_module_t結構來供使用,它為framebuffer的初始化提供了需要的gralloc.msm7x30.so業務。因此通過這個hw_module_t結構我們就不需要知道當前系統使用的到底是哪個gralloc library。按規定,所有gralloc library中的這個結構體被命名為HAL_MODULE_INFO_SYM(HMI)。當前分析的系統中,HAL_MODULE_INFO_SYMhardware/msm7k/libgralloc-qsd8k/galloc.cpp

    1.1.2 開啟fbDev裝置符    

   下面看如何開啟 開啟fbDev裝置符。通過HAL_MODULE_INFO_SYM提供的gralloc.msm7x30.so的介面我們呼叫到了fb_device_open()@hardware/msm7k/libgralloc-qsd8kframebuffer.cpp

 

[cpp] view plaincopy

1. int fb_device_open(hw_module_t const* module, const char* name,  

2.         hw_device_t** device)  

3. {  

4.     int status = -EINVAL;  

5.     if (!strcmp(name, GRALLOC_HARDWARE_FB0)) {  

6.         alloc_device_t* gralloc_device;  

7.         status = gralloc_open(module, &gralloc_device);  

8.   

9.         /* initialize our state here */  

10.         fb_context_t *dev = (fb_context_t*)malloc(sizeof(*dev));  

11.         memset(dev, 0, sizeof(*dev));  

12.   

13.         /* initialize the procs */  

14.         dev->device.common.tag = HARDWARE_DEVICE_TAG;  

15.   

16.         private_module_t* m = (private_module_t*)module;  

17.         status = mapFrameBuffer(m);  

18.   

19. }  

 

在這個函式中,主要為fbDev裝置符指定一個fb_context_t例項,並通過函式mapFrameBuffer()對裝置節點/dev/graphics/fb0進行操作,操作的目的有:

1.獲得螢幕裝置的資訊,並將螢幕資訊儲存在HAL_MODULE_INFO_SYM(上面程式碼中的module)中。

 2./dev/graphics/fb0請求page flip模式,page

 flip模式需要至少2個螢幕大小的bufferpage flip模式在後面介紹。目前android系統中設定為2個螢幕大小的buffer。當然螢幕裝置可能不支援page flip模式。

mapFrameBufferLocked()@hardware/msm7k/libgralloc-qsd8k/framebuffer.cpp

[cpp] view plaincopy

1. /* 

2.  * Request NUM_BUFFERS screens (at lest 2 for page flipping) 

3.  */  

4. info.yres_virtual = info.yres * NUM_BUFFERS;  

5.   

6.   

7. uint32_t flags = PAGE_FLIP;  

8. if (ioctl(fd, FBIOPUT_VSCREENINFO, &info) == -1) {  

9.     info.yres_virtual = info.yres;  

10.     flags &= ~PAGE_FLIP;  

11.     LOGW("FBIOPUT_VSCREENINFO failed, page flipping not supported");  

12. }  

 

3. 對映螢幕裝置快取區給fbDev裝置符。

mapFrameBufferLocked()@hardware/msm7k/libgralloc-qsd8k/framebuffer.cpp

[cpp] view plaincopy

1. /* 

2.  * map the framebuffer 

3.  */  

4.   

5. int err;  

6. size_t fbSize = roundUpToPageSize(finfo.line_length * info.yres_virtual);  

7. module->framebuffer = new private_handle_t(dup(fd), fbSize,  

8.         private_handle_t::PRIV_FLAGS_USES_PMEM);  

9.   

10. module->numBuffers = info.yres_virtual / info.yres;  

11. module->bufferMask = 0;  

12.   

13. void* vaddr = mmap(0, fbSize, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);  

14. if (vaddr == MAP_FAILED) {  

15.     LOGE("Error mapping the framebuffer (%s)", strerror(errno));  

16.     return -errno;  

17. }  

18. module->framebuffer->base = intptr_t(vaddr);  

19. memset(vaddr, 0, fbSize);  

 

 

1.2 grDev裝置符

在為framebuffer,也就是FramebufferNativeWindow申請記憶體之前,我們還要介紹一個概念,就是grDev裝置符。它雖然也叫裝置符,但是它和具體的裝置沒有直接關係,我們看它的型別就是知道了alloc_device_t,沒錯,grDev裝置符就是為了FramebufferNativeWindow管理記憶體使用的。為FramebufferNativeWindow提供了申請/釋放記憶體的介面。



    1.3 FramebufferNativeWindow記憶體管理

    FramebufferNativeWindow維護了2buffer 

[cpp] view plaincopy

1. sp<NativeBuffer> buffers[2];  

 

    1.3.1 螢幕裝置支援page filp模式

    目前的android系統預設要求螢幕裝置給系統對映2個螢幕大小的快取區,以便支援pageflip模式,如果螢幕裝置支援pageflip模式,那麼FramebufferNativeWindowbuffers將分別指向一個螢幕大小的螢幕裝置快取區。

[cpp] view plaincopy

1. // create a "fake" handles for it  

2. intptr_t vaddr = intptr_t(m->framebuffer->base);  

3. private_handle_t* hnd = new private_handle_t(dup(m->framebuffer->fd), size,  

4.                                              private_handle_t::PRIV_FLAGS_USES_PMEM |  

5.                                              private_handle_t::PRIV_FLAGS_FRAMEBUFFER);  

6.   

7. // find a free slot  

8. for (uint32_t i=0 ; i<numBuffers ; i++) {  

9.     if ((bufferMask & (1LU<<i)) == 0) {  

10.         m->bufferMask |= (1LU<<i);  

11.         break;  

12.     }  

13.     vaddr += bufferSize;  

14. }  

15.   

16. hnd->base = vaddr;  

17. hnd->offset = vaddr - intptr_t(m->framebuffer->base);  

18. *pHandle = hnd;  

 

    1.3.2 螢幕裝置不支援page flip模式

    mapFrameBufferLocked()@hardware/msm7k/libgralloc-qsd8k/framebuffer.cpp中可以得知,如果螢幕裝置不支援pageflip模式,那麼numBuffer值將為1而不是2,那麼對映過來的螢幕快取區將只有一個螢幕大小,不夠支援pageflip模式,那麼此時將不使用這一個螢幕大小的螢幕快取區,而改為去dev/pmem裝置去申請。

gralloc_alloc_framebuffer_locked()@hardware/msm7k/libgralloc-qsd8k/gpu.cpp

[cpp] view plaincopy

1.     const uint32_t bufferMask = m->bufferMask;  

2.     const uint32_t numBuffers = m->numBuffers;  

3.     const size_t bufferSize = m->finfo.line_length * m->info.yres;  

4.     if (numBuffers == 1) {  

5.         // If we have only one buffer, we never use page-flipping. Instead,  

6.         // we return a regular buffer which will be memcpy'ed to the main  

7.         // screen when post is called.  

8.         int newUsage = (usage & ~GRALLOC_USAGE_HW_FB) | GRALLOC_USAGE_HW_2D;  

9.         return gralloc_alloc_buffer(bufferSize, newUsage, pHandle);  

10.     }  

 

    2. 開啟Overlay

    同選擇gralloc library相似,根據屬性值來選擇何時的overlay庫,如果vendor廠商沒有提供overlay庫的話,那麼系統將使用預設的overlayoverlay.default.so。同樣的我們獲得overlay庫的HAL_MODULE_INFO_SYM結構體,作為系統呼叫overlay的介面。

 

[cpp] view plaincopy

1. if (hw_get_module(OVERLAY_HARDWARE_MODULE_ID, &module) == 0) {  

2.     overlay_control_open(module, &mOverlayEngine);  

3. }  

 

    3. 選擇OpenGL ES library(也即軟/硬體加速)

    OpenGL(Open Graphics Library)[3] is a standard specification defining across-language, cross-platform API for writing applications that produce 2D and3D computer graphics. The interface consists of over 250 different functioncalls which can be used to draw complex three-dimensional scenes from simpleprimitives. OpenGL was developed by Silicon Graphics Inc. (SGI) in 1992[4] andis widely used in CAD, virtual reality, scientific visualization, informationvisualization, flight simulation, and video games. OpenGL is managed by thenon-profit technology consortium Khronos Group.

    android是預設支援OpenGL ES軟體加速的,librarylibGLES_android,原始碼路徑為frameworks\base\opengl\libagl;如果手機裝置支援硬體加速的話,那麼複雜的影象處理工作將交由GPU去處理,那麼效率將大大提高。但是如果系統真的存在硬體加速,它是如何選擇何時用軟體加速?何時用硬體加速的呢?

    如何檢視是否有GPU來實現硬體加速,很容易檢視/system/lib/egl/egl.cfg檔案內容

[java] view plaincopy

1. 0 0 android  

2. 0 1 adreno200  

    因此只要我們的移動裝置晶片集成了GPU,並提供了對應的GL圖形庫,那麼我們就可以在我們的工程中device目錄下的egl.cfg檔案中加入類似上面的配置,那麼我們的系統就會支援硬體加速。

adreno200 GPU提供的GL圖形庫:

[cpp] view plaincopy

1. libGLESv1_CM_adreno200.so  

2. libGLESv2_adreno200.so  

3. libEGL_adreno200.so  

    那麼假如我們的系統中軟硬體加速都支援了,那麼我們從程式碼來看能不能讓使用者自由的選擇加速型別,我們帶著問題來研究一下程式碼。

   3.1 OpenGL初始化

    在呼叫不管是軟體加速的還是硬體加速的OpenGL api之前,我們都需要把軟硬兩種模式的各自的OpenGL api提取出來,抽象出一個interface來供系統使用,這個過程我稱之為OpenGL初始化過程。

    軟硬兩種模式的OpenGL api被分別指定到了一個全域性陣列的對應位置。
frameworks/base/opengl/libs/EGL/egl.cpp

[cpp] view plaincopy

1. static egl_connection_t gEGLImpl[IMPL_NUM_IMPLEMENTATIONS];  

[cpp] view plaincopy

1. enum {  

2.     IMPL_HARDWARE = 0,  

3.     IMPL_SOFTWARE,  

4.     IMPL_NUM_IMPLEMENTATIONS  

5. };  


gEGLImpl[IMPL_HARDWARE]
中儲存著硬體圖形裝置的OpenGL api地址,從

[cpp] view plaincopy

1. libGLESv1_CM_adreno200.so  

2. libGLESv2_adreno200.so  

3. libEGL_adreno200.so  

3個庫中獲得;gEGLImpl[IMPL_SOFTWARE]中儲存著軟體的OpenGL api地址,從libGLES_android.so中獲取。

 

這部分程式碼在egl_init_drivers_locked()@frameworks/base/opengl/libs/EGL/egl.cpp

 

3.2 EGLGLES api

    OpenGL的初始化過程中,OpenGL提供了兩套api,分別稱為EGLGLESandroidOPENGL初始化過程中,會將兩種不同的介面分開管理,從下面程式碼中我們可以看到EGLGLES api地址被儲存到了不同的位置。

@frameworks\base\opengl\libs\EGL\Loader.h

[cpp] view plaincopy

1. enum {  

2.     EGL         = 0x01,  

3.     GLESv1_CM   = 0x02,  

4.     GLESv2      = 0x04  

5. };  

load_driver()@frameworks\base\opengl\libs\EGL\Loader.cpp

 

上面列舉的EGL表示ELG apiGLESvq1_CM表示OpenGL ES 1.0apiGLESv2表示OpenGL ES 2.0api

EGL api地址最終被儲存在gEGLImpl[].egl中;

GLESvq1_CM api地址最終被儲存在gEGLImpl[].hooks[GLESv1_INDEX]->gl中;

GLESv2 api地址最終被儲存在gEGLImpl[].hooks[GLESv2_INDEX]->gl中;

 

3.2.1 EGL api

    EGLis an interface between Khronos rendering APIs such as OpenGL ES or OpenVG andthe underlying native platform window system. It handles graphics contextmanagement, surface/buffer binding, and rendering synchronization and enableshigh-performance, accelerated, mixed-mode 2D and 3D rendering using otherKhronos APIs.

   上面引用了官方的定義,可以看出,EGL是系統和OPENGL ES之間的介面,它的宣告在檔案frameworks\base\opengl\libs\EGL\egl_entries.in

 

3.2.2 GLES

    GLES才是真正的OpenGL ESapi,它的宣告我們可以在frameworks\base\opengl\libs\entries.in找到。目前的android系統不但將EGL提供給系統使用,同時將GLES也提供給了系統使用,這個我們可以在最開始的顯示系統的結構圖中可以看到,surfacefligerframeworkopengl模組均可以訪問EGLGLES介面。

 

3.3 OpenGL config

    每個OpenGL庫都根據不同的畫素格式(pixelformat)提供了一系統的configandroid根據framebuffer中設定的畫素格式來選擇合適的configandroid根據中各config中的屬性資訊來建立main surfaceopenGL上下文。

3.3.1 系統預設pixel format

    當前的程式碼分析是基於gingerbread的,在mapFrameBufferLocked()@hardware/msm7k/libgralloc-qsd8k/framebuffer.cpp中我們可以找到framebufferpixel format的型別

[cpp] view plaincopy

1.    if(info.bits_per_pixel == 32) {  

2. /* 

3. * Explicitly request RGBA_8888 

4. */  

5.   

6. /* Note: the GL driver does not have a r=8 g=8 b=8 a=0 config, so if we do 

7. * not use the MDP for composition (i.e. hw composition == 0), ask for 

8. * RGBA instead of RGBX. */  

9. if (property_get("debug.sf.hw", property, NULL) > 0 && atoi(property) == 0)  

10.     module->fbFormat = HAL_PIXEL_FORMAT_RGBX_8888;  

11. else if(property_get("debug.composition.type", property, NULL) > 0 && (strncmp(property, "mdp", 3) == 0))  

12.     module->fbFormat = HAL_PIXEL_FORMAT_RGBX_8888;  

13. else  

14.     module->fbFormat = HAL_PIXEL_FORMAT_RGBA_8888;  

15.    } else {  

16. /* 

17. * Explicitly request 5/6/5 

18. */  

19. module->fbFormat = HAL_PIXEL_FORMAT_RGB_565;  

20.    }  



目前的移動裝置都是真彩色,所以這裡我們認為我們的螢幕裝置支援的是HAL_PIXEL_FORMAT_RGBA_8888

    

3.3.2config初始化

所有的OpenGL庫提供的config,同樣需要將軟硬兩種模式的各自的OpenGL config提取出來供系統使用,如同OpenGL api地址一樣。OpenGL config提取出來後儲存在另外一個全域性變數

[cpp] view plaincopy

1. static egl_display_t gDisplay[NUM_DISPLAYS];  

[cpp] view plaincopy

1. //  EGLDisplay are global, not attached to a given thread  

2. const unsigned int NUM_DISPLAYS = 1;  

中,不同於gEGLImpl分開儲存軟硬體api,所有的config,不論軟硬體的,均儲存在gDisplay[0],因為所有的config是以螢幕區分的,同一塊螢幕應該儲存同一份config資訊。



在提取出的openGLconfig時,會儲存到gDisplay[0].config中,在這兒有一個很tricky的實現,它保證了硬體加速器的優先使用!



[cpp] view plaincopy

1. <strong>  </strong>      // sort our configurations so we can do binary-searches  

2.         qsort(  dp->configs,  

3.                 dp->numTotalConfigs,  

4.                 sizeof(egl_config_t), cmp_configs);<strong>  

5. </strong>  

最終,上述程式碼會將gDisplay[0].config中的配置按照先硬體的,後軟體的規則做一個總體的排序。

程式碼在eglInitialize()@frameworks/base/opengl/libs/EGL/egl.cpp



3.3.3config選擇

上文說到,android會根據framebufferpixel format資訊來獲取對應的config,這個過程只選擇一個合適的config,選到為止。



3.3.3.1滿足屬性要求

並不是所有的config都可以被選擇,首先這個config的屬性需要滿足

init()@DisplayHardware.cpp

[cpp] view plaincopy

1. // initialize EGL  

2. EGLint attribs[] = {  

3.         EGL_SURFACE_TYPE,   EGL_WINDOW_BIT,  

4.         EGL_NONE,           0,  

5.         EGL_NONE  

6. };  

3.3.3.2滿足RGBA要求

pixelflinger中,為系統提供了各個pixel format的基本資訊,RGBA值,位元組數/pixel,位數/pixel

system/core/libpixelflinger/format.cpp

[cpp] view plaincopy

1. static GGLFormat const gPixelFormatInfos[] =  

2. {   //          Alpha    Red     Green   Blue  

3.     {  0,  0, {{ 0, 0,   0, 0,   0, 0,   0, 0 }},        0 },   // PIXEL_FORMAT_NONE  

4.     {  4, 32, {{32,24,   8, 0,  16, 8,  24,16 }}, GGL_RGBA },   // PIXEL_FORMAT_RGBA_8888  

android會根據pixelflingerpixel

 format資訊,去和openGLconfig比較,得到想要的config



selectConfigForPixelFormat()@frameworks/base/libs/ui/EGLUtils.cpp

[cpp] view plaincopy

1. EGLConfig* const configs = (EGLConfig*)malloc(sizeof(EGLConfig)*numConfigs);  

2. if (eglChooseConfig(dpy, attrs, configs, numConfigs, &n) == EGL_FALSE) {  

3.     free(configs);  

4.     return BAD_VALUE;  

5. }  

6.   

7. const int fbSzA = fbFormatInfo.getSize(PixelFormatInfo::INDEX_ALPHA);  

8. const int fbSzR = fbFormatInfo.getSize(PixelFormatInfo::INDEX_RED);  

9. const int fbSzG = fbFormatInfo.getSize(PixelFormatInfo::INDEX_GREEN);  

10. const int fbSzB = fbFormatInfo.getSize(PixelFormatInfo::INDEX_BLUE);   

11.   

12. int i;  

13. EGLConfig config = NULL;  

14. for (i=0 ; i<n ; i++) {  

15.     EGLint r,g,b,a;  

16.     EGLConfig curr = configs[i];  

17.     eglGetConfigAttrib(dpy, curr, EGL_RED_SIZE,   &r);  

18.     eglGetConfigAttrib(dpy, curr, EGL_GREEN_SIZE, &g);  

19.     eglGetConfigAttrib(dpy, curr, EGL_BLUE_SIZE,  &b);  

20.     eglGetConfigAttrib(dpy, curr, EGL_ALPHA_SIZE, &a);  

21.     if (fbSzA <= a && fbSzR <= r && fbSzG <= g && fbSzB  <= b) {  

22.         config = curr;  

23.         break;  

24.     }  

25. }  

 

    4. 建立main surface

    要讓OpenGL進行圖形處理,那麼需要在OpenGL中建立一個openGL surface。程式碼在eglCreateWindowSurface()@frameworks/base/opengl/libs/EGL/egl.cpp

呼叫當前的config所處的openGL庫的api來建立surface。通過validate_display_config()方法來獲取當前configopenGL api

建立的surface會和FramebufferNativeWindow關聯到一起。

    5. 建立 OpenGL ES 上下文

   An OpenGL context represents many things. A context storesall of the state associated with this instance of OpenGL. It represents the(potentially visible) default framebufferthat rendering commands will draw to when not drawing to a framebuffer object. Think of a context as an object that holdsall of OpenGL; when a context is destroyed, OpenGL is destroyed.

  http://www.opengl.org/wiki/OpenGL_context

 具體的建立過程專業術語太多,也沒有仔細研究不再介紹。

    6. 繫結contextsurface

    有了surface,有了FramebufferNativeWindow,有了context,基本上與圖形系統相關的概念都有了,下一步就是把這幾個概念關聯起來,在建立surface時已經將surfaceFramebufferNativeWindow關聯了起來。

   eglMakeCurrent()@frameworks/base/opengl/libs/EGL/egl.cpp

6.1多執行緒支援

OpenGL 提供了多執行緒的支援,有以下2點的支援:

1. 一個Context只能被一個執行緒使用,不能存在多個執行緒使用同一個context。因此在多線層操作中使用到了TLS技術,即Thread-local storage,來保證context被唯一使用。

makeCurrent()@frameworks/base/opengl/libs/libagl/egl.cpp

[cpp] view plaincopy

1.     ogles_context_t* current = (ogles_context_t*)getGlThreadSpecific();  

2.     if (gl) {  

3.         egl_context_t* c = egl_context_t::context(gl);  

4.         if (c->flags & egl_context_t::IS_CURRENT) {  

5.             if (current != gl) {  

6.                 // it is an error to set a context current, if it's already  

7.