Android Native Server 개념에 크게 Surface Flinger와 Audio Flinger란 것이 있다.
사실 Flinger란 단어가 참 낯설게 느껴지는데 사전적인 의미로는 투수(무언가 던지거나 차는 사람)란 의미가 가장 의미가 와 닿는 것 같은데 Application에서의 데이터를 한데 모아서 출력쪽으로 던져주는 역활을 한다는 의미로 사용한 것이 아닐까 싶다. (언제나 그렇듯이 추측이다)
Surface Flinger는 단연히 여러가지 Surface 데이터를 한데 모아서 Frame Buffer를 업데이트하는 것이고 Audio Flinger는 여러가지 오디오 샘플을 한데 모아서, 즉 Audio Mixing을 해서 ALSA 쪽으로 보내는 역활을 한다고 볼 수 있다.
Surface Flinger에 대한 가장 잘 표현된 그림은 다음과 같다.
Surface Flinger가 하는 기본적인 내용을 번역해 보면 다음과 같다.
- Provides system-wide surface "composer", handling all surface rendering to frame buffer device
composer라고 하는 surface들을 합치는 기능을 제공한다는 의미인 것 같다. (비슷한 말들이 계속 나온다)
- Can combine 2D and 3D surfaces and surfaces from multiple applications
2D, 3D surface를 결합하거나, 여러 응용들의 surface를 결합할 수 있다. 이 말이 결국 Suface Flinger의 역활을 한마디로 표현한 것이 아닌가 싶다.
- Surfaces passed as buffers via Binder IPC calls
Binder IPC calls에 의하여 버퍼를 패스한다. (사실 아직 Binder란 개념이 낯설어서 이 말은 이해가 전혀 안된다.)
- Can use OpenGL ES and 2D hardware accelerator for its compositions
Surface 들을 결합하기 위하여 OpenGL ES와 2D H/W 가속을 이용할 수 있다.
- Double-buffering using page-flip
Page Flip을 위해서 더블 버퍼링을 사용한다.
결론적으로 OpenGL ES와 연동이 되면서 Surface들을 합쳐서 Frame Buffer로 전달할 수 있는 구조이며 page flip을 위해 double buffering을 사용한다는 것으로 요약할 수 있을 것이다.
OpenGL ES와 연동하기 위하여 Surface Flinger의 초기화 과정을 보면 EGL을 이용하여 Display와 Surface 등을 생성하는 과정을 확인할 수 있다.
관련 소스를 보면 다음과 같다.
\frameworks\base\libs\surfaceflinger\SurfaceFlinger.cpp
\frameworks\base\libs\surfaceflinger\displayhardware\DisplayHardware.cpp
\frameworks\base\libs\ui\EGLDisplaySurface.cpp
\frameworks\base\opengl\libagl\Egl.cpp
SurfaceFlinger.cpp를 보면 SurfaceFlinger 클래스는 여러 클래스로부터 다중 상속된 클래스이다. 특히 Thread 클래스로부터 상속된 클래스인테 이는 실행되면 하나의 threadLoop()가 동작함을 의미한다. 이 threadLoop가 동작하기 전에 readyToRun()으로 초기화 과정이 이루어 진다.
SurfaceFlinger의 readyToRun()을 보면 초기화 과정 중 main display를 초기화 하는 과정과 OpenGL ES를 초기화하는 과정이 있음을 알 수 있다. main display를 초기화 하는 과정을 보면 다음과 같다.
status_t SurfaceFlinger::readyToRun()
{
......
// initialize the main display
GraphicPlane& plane(graphicPlane(dpy));
DisplayHardware* const hw = new DisplayHardware(this, dpy);
plane.setDisplayHardware(hw);
}
즉 DisplayHardware를 생성하는 과정이 필요하다. 생성되는 DisplayHaraware는 GraphicPlane에 설정된다.
DisplayHardware 클래스를 보면 init 함수에서 EGL 관련된 내용들을 초기화 하고 있다.
DisplayHardware::DisplayHardware(
const sp<SurfaceFlinger>& flinger,
uint32_t dpy)
: DisplayHardwareBase(flinger, dpy)
{
init(dpy);
}
init() 함수에서 하는 내용을 순서대로 정리하면 다음과 같다.
1) Display 정보 얻어서 EGL 초기화
- EGL extenstion 정보 얻는 과정도 포함하며 이를 이용하여 mFlag에 조건 저장
2) Surface 생성
3) OpenGL ES의 Context 생성
4) OpenGL ES의 Extension 정보 얻음
- 이를 위해서 Display와 Surface, Context를 bind 하는 과정 있음, 모든 정보를 얻으면 다시 unbind
- Extension 정보를 저장하여 mFlag에 조건 저장
5) 생성된 내용을 저장
6) Hardware Module 초기화
- copybit
- overlay
init()에서 하는 과정은 대부분은 EGL 초기화 과정과 디바이스 드라이버(특히 fb, scaler 등)를 여는 것이라고 볼 수 있으며 이에 대한 내용은
http://www.khronos.org/registry/egl/specs/eglspec.1.3.pdf 을 참고하는 것이 좋다.
1) Display 정보 얻어서 EGL 초기화
void DisplayHardware::init(uint32_t dpy)
{
......
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(display, NULL, NULL);
eglGetConfigs(display, NULL, 0, &numConfigs);
eglChooseConfig(display, attribs, &config, 1, &n);
}
2) Surface 생성
Surface 생성은 크게 EGLDisplaySurface 클래스를 생성하는 과정과 이로부터 실제 EGL의 windows surface를 생성하는 과정으로 나뉘어 진다.
EGL은 세종류의 surface type(windows, pbuffers, pixmaps)를 지원하는데 windows는 onscreen rendering에 사용되고, pbuffers는 offscreen rendering에 사용되며 pixmaps는 nativa API로 접근하는 버퍼들에 대한 offscreen rendering에 사용된다. 말이 좀 어려운데 결국 onscreen rendering은 우리가 보통 얘기하는 frame buffer와 직접적인 관련이 있다는 것을 알 수 있다.
결국 여기에서 frame buffer로의 기본 출력 (fb device와의 연결)을 위한 기본 동작이 수행된다.
void DisplayHardware::init(uint32_t dpy)
{
......
/*
* Create our main surface
*/
mDisplaySurface = new EGLDisplaySurface();
surface = eglCreateWindowSurface(display, config, mDisplaySurface.get(), NULL);
//checkEGLErrors("eglCreateDisplaySurfaceANDROID");
if (eglQuerySurface(display, surface, EGL_SWAP_BEHAVIOR, &dummy) == EGL_TRUE) {
if (dummy == EGL_BUFFER_PRESERVED) {
mFlags |= BUFFER_PRESERVED;
}
}
GLint value = EGL_UNKNOWN;
eglQuerySurface(display, surface, EGL_HORIZONTAL_RESOLUTION, &value);
if (value == EGL_UNKNOWN) {
mDpiX = 160.0f;
} else {
mDpiX = 25.4f * float(value)/EGL_DISPLAY_SCALING;
}
value = EGL_UNKNOWN;
eglQuerySurface(display, surface, EGL_VERTICAL_RESOLUTION, &value);
if (value == EGL_UNKNOWN) {
mDpiY = 160.0f;
} else {
mDpiY = 25.4f * float(value)/EGL_DISPLAY_SCALING;
}
mRefreshRate = 60.f;
}
EGLDisplaySurface 클래스를 생성하는 것을 보면 mapFrameBuffer()를 호출하는데
EGLDisplaySurface::EGLDisplaySurface()
: EGLNativeSurface<EGLDisplaySurface>()
{
egl_native_window_t::version = sizeof(egl_native_window_t);
egl_native_window_t::ident = 0;
egl_native_window_t::incRef = &EGLDisplaySurface::hook_incRef;
egl_native_window_t::decRef = &EGLDisplaySurface::hook_decRef;
egl_native_window_t::swapBuffers = &EGLDisplaySurface::hook_swapBuffers;
egl_native_window_t::connect = 0;
egl_native_window_t::disconnect = 0;
mFb[0].data = 0;
mFb[1].data = 0;
mBlitEngine = 0;
egl_native_window_t::fd = mapFrameBuffer();
......
}
status_t EGLDisplaySurface::mapFrameBuffer()
{
char const * const device_template[] = {
"/dev/graphics/fb%u",
"/dev/fb%u",
0 };
int fd = -1;
int i=0;
char name[64];
while ((fd==-1) && device_template[i]) {
snprintf(name, 64, device_template[i], 0);
fd = open(name, O_RDWR, 0);
i++;
}
if (fd < 0)
return -errno;
struct fb_fix_screeninfo finfo;
if (ioctl(fd, FBIOGET_FSCREENINFO, &finfo) == -1)
return -errno;
struct fb_var_screeninfo info;
if (ioctl(fd, FBIOGET_VSCREENINFO, &info) == -1)
return -errno;
......
/*
* Open and map the display.
*/
void* buffer = (uint16_t*) mmap(
0, finfo.smem_len,
PROT_READ | PROT_WRITE,
MAP_SHARED,
fd, 0);
if (buffer == MAP_FAILED)
return -errno;
// at least for now, always clear the fb
memset(buffer, 0, finfo.smem_len);
uint8_t* offscreen[2];
offscreen[0] = (uint8_t*)buffer;
if (flags & PAGE_FLIP) {
offscreen[1] = (uint8_t*)buffer + finfo.line_length*info.yres;
} else {
offscreen[1] = (uint8_t*)malloc(finfo.smem_len);
if (offscreen[1] == 0) {
munmap(buffer, finfo.smem_len);
return NO_MEMORY;
}
}
mFlags = flags;
mInfo = info;
mFinfo = finfo;
mSize = finfo.smem_len;
mIndex = 0;
for (int i=0 ; i<2 ; i++) {
mFb[i].version = sizeof(GGLSurface);
mFb[i].width = info.xres;
mFb[i].height = info.yres;
mFb[i].stride = finfo.line_length / (info.bits_per_pixel >> 3);
mFb[i].data = (GGLubyte*)(offscreen[i]);
mFb[i].format = GGL_PIXEL_FORMAT_RGB_565;
}
return fd;
}
간단히 정리하면 여기에서는
1) fb device를 찾아서 열고
2) FSCREEN_INFO와 VSCREEN_INFO를 얻은 후,
3) mmap()으로 fb device에 직접 억세스할 수 있는 버퍼 포인터를 얻고 (mmap은 리눅스의 char형 Device Driver를 File I/O 형태로 접근하지 않고 pointer 형태로 접근하도록 해준다. 이와 관련된 내용은 추후 다시 정리)
4) 이를 mFb에 저장한다.
즉 EGLDisplaySurface 클래스를 만듬으로써 SurfaceFlinger가 실제 Fb Devie Driver로 접근하기 위한 준비를 하게 되는 것이다. eglCreateWindowSurface를 호출하여 window surface를 만드는 것은 EGL을 사용하기 위하여 반드시 필요한 과정이다. 또한 이 과정이 EGLDisplaySurface 클래스로 생성된 내용을 실제 연결하는 과정이라고 생각해도 될 것 같다.
3) OpenGL ES의 Context 생성은 원래 EGL의 초기화 과정 중의 하나이다. 부담없이 보면 다음과 같다. context를 생성한 후, width와 height 정보를 얻어서 저장한다.
void DisplayHardware::init(uint32_t dpy)
{
......
/*
* Create our OpenGL ES context
*/
context = eglCreateContext(display, config, NULL, NULL);
//checkEGLErrors("eglCreateContext");
eglQuerySurface(display, surface, EGL_WIDTH, &mWidth);
eglQuerySurface(display, surface, EGL_HEIGHT, &mHeight);
}
4)의 과정은 정보를 얻기 위하여 display, surface, context를 bind, unbind 하는 과정을 볼 수 있다는 것 외에 특별히 유의 사항은 없을 것 같다. 5)의 과정도 생성된 내용을 저장해 둔다는 것에만 주의하면 된다.
6) Hardware Module 초기화 과정에서는 2개의 중요한 모듈 copybit과 overlay를 초기화 한다. 6의 과정은 여기에서 하드웨어 모듈이 초기화 된다는 것이 중요하다. 이러한 하드웨어 모듈은 다시 말하면 GPU가 제공해주는 기능이다. 안드로이드의 경우 다양한 CPU Platform을 지원할 수 있고, 이는 같은 ARM 코어를 사용하는 경우라도 GPU 인터페이스는 다르다는 것을 의미한다. 즉 copybit 과 overlay 엔진에 대한 코드는 Chip vendor에서 만들어 두고 제공해 주어야만 바로 여기에서 그 장치를 열고 추후에 Surface Flinger를 사용할 때, CPU Platform의 고유한 성능을 낼 수 있는 것이다.
void DisplayHardware::init(uint32_t dpy)
{
......
hw_module_t const* module;
mBlitEngine = NULL;
if (hw_get_module(COPYBIT_HARDWARE_MODULE_ID, &module) == 0) {
copybit_open(module, &mBlitEngine);
}
mOverlayEngine = NULL;
if (hw_get_module(OVERLAY_HARDWARE_MODULE_ID, &module) == 0) {
overlay_control_open(module, &mOverlayEngine);
}
}
copybit에 대해서는 추후에 더 정리해야 겠지만 bitblt와 stretchbitblt를 지원하는 모듈이다. 여기에서 무슨 기능인지 알 수 있는 사람도 있겠지만 다시 설명하면 이미지 드로잉에서 많이 사용하는 복사와 Scaleing을 지원하는 장치라고 생각하면 될 것 같다. 즉 fb device 외에도 GPU의 scaler와 copy 기능을 위한 장치도 여기에서 열리게 된다.
초기화 과정에서 얻은 EGL의 정보로부터 mFlag에 특정 조건을 저장하는데 이는 나중에 Surface Flinger의 더블 버퍼 스왑 등의 처리 조건으로 사용된다. 이는 추후 다시 정리한다.
대략적인 초기화 과정에 대한 설명은 이것으로 끝이다. 그런데 왜 Surface Flinger의 초기화 과정 대부분이 EGL 초기화 과정일까?
모 이에 대한 답은 추론할 수 밖에 없다. 안드로이는 처음부터 2D Drawing은 Skia를 이용하면서 OpenGL ES를 지원하도록 설계되어 있다. OpenGL ES를 지원하려면 EGL를 이용하여 Native Window와 연결할 수 있는 구조를 만들어 주어야 한다. 따라서 일반적인 Embedded System의 단순한 GDI 함수와 Screen Buffer (Frame Buffer)의 개념이 아닌 EGL를 통한 display, surface, context를 이용한 개념으로 부터 출발할 수 밖에 없다. 만약 OpenGL ES를 지원하지 않도록 설계되었다면 Surface Flinger라고 하지 않고 Display Flinger나 Screen Flinger라고 했을 수도 있을 것 같다.
이런 개념 때문에 OpenGL 등을 잘 모르는 Embedded Programer 입장에서는 단순히 GDI 개념을 이해하기에도 머리 복잡한 내용이 너무 많아진다. 당연히 fb 디바이스 드라이버만 오픈하고 이렇게 오픈된 디바이스로부터 mmap으로 메모리 포인터를 얻어와서 GDI 함수를 구현하는 것에 익숙해져 있는데 (사실 이것도 리눅스 하는 사람 아니면 익숙하지 않다) 여기에 한가지 개념이 더 포함된 것이다.
하여간 추측으로 분석하고 적당히 마무리하지만 안드로이드 시스템에서 표준 방식으로 fb 디바이스 드라이버를 여는 곳은 여기이고 이의 구조를 이해해야만 middleware의 접근이 쉬워진다.
기타 참고로 부팅시 로그를 받아보면 다음과 같다.
I/SurfaceFlinger( 739): SurfaceFlinger is starting
I/SurfaceFlinger( 739): showcpu enabled
I/SurfaceFlinger( 739): showfps enabled
I/SurfaceFlinger( 739): SurfaceFlinger's main thread ready to run. Initializing graphics H/W...
I/SurfaceFlinger( 739): EGL informations:
I/SurfaceFlinger( 739): # of configs : 6
I/SurfaceFlinger( 739): vendor : Android
I/SurfaceFlinger( 739): version : 1.31 Android META-EGL
I/SurfaceFlinger( 739): extensions:
I/SurfaceFlinger( 739): Client API: OpenGL ES
I/EGLDisplaySurface( 739): using (fd=21)
I/EGLDisplaySurface( 739): id = xxx_fb
I/EGLDisplaySurface( 739): xres = 800 px
I/EGLDisplaySurface( 739): yres = 480 px
I/EGLDisplaySurface( 739): xres_virtual = 800 px
I/EGLDisplaySurface( 739): yres_virtual = 960 px
I/EGLDisplaySurface( 739): bpp = 16
I/EGLDisplaySurface( 739): r = 11:5
I/EGLDisplaySurface( 739): g = 5:6
I/EGLDisplaySurface( 739): b = 0:5
I/EGLDisplaySurface( 739): width = 800 mm (25.400000 dpi)
I/EGLDisplaySurface( 739): height = 480 mm (25.400000 dpi)
I/EGLDisplaySurface( 739): refresh rate = 60.00 Hz
W/SurfaceFlinger( 739): ro.sf.lcd_density not defined, using 160 dpi by default.
I/SurfaceFlinger( 739): OpenGL informations:
I/SurfaceFlinger( 739): vendor : Android
I/SurfaceFlinger( 739): renderer : Android PixelFlinger 1.0
I/SurfaceFlinger( 739): version : OpenGL ES-CM 1.0
I/SurfaceFlinger( 739): extensions: GL_OES_byte_coordinates GL_OES_fixed_point GL_OES_single_precision GL_OES_read_format G
L_OES_compressed_paletted_texture GL_OES_draw_texture GL_OES_matrix_get GL_OES_query_matrix GL_ARB_texture_compression GL_AR
B_texture_non_power_of_two GL_ANDROID_direct_texture GL_ANDROID_user_clip_plane GL_ANDROID_vertex_buffer_object GL_ANDROID_g
enerate_mipmap