surfaceflinger 분석

Android Graphic Pipeline

Android Developer에 보면 Android가 화면을 잘 설명해 놓은 그림이 있다.

필자는 여기에다 파란색 선만 하나 더 그어 놓았는데, 이 선이 VSYNC 이다. VSYNC는 16.7ms마다 들어오는 신호이다. 중구난방으로 화면을 그리다 보면 화면이 분명 깨질 것이다. 그러므로 Android는 일정 주기마다 이때만 화면을 그리라고 VSYNC 신호를 준다.

하나씩 순서대로 알아보자

  1. 노란색 박스
    APP의 Window 들이다. 각 Window 들을 가지고 있는 App은 Render Thread를 실행시켜 Window 화면을 그린다. EGL과 OpenGLES 를 이용해 GPU로 그림을 그리고, 그 결과물을 Buffer Queue에 넣는다.
  2. 노란 박스 옆 GPU 박스
    따로 분리되어 있는 이유는 Render Thread를 사용한다는걸 강조하기 위함이라고 추측해본다. Android-L 부터 Render Thread가 도입되면서 UI Thread와 Render Thread가 Pipelining으로 진행이 가능하다.
  3. Buffer Queue
    Buffer Queue는 생산자(Producer)와 소비자(Consumer)가 각각 접근해 사용한다. App은 생산자이고, SurfaceFlinger는 소비자이다.
    생산자는 dequeBuffer 함수로 내용물이 빈 Buffer를 얻고, queueBuffer 함수로 채운 버퍼를 돌려준다.
    소비자는 acqureBuffer 함수로 내용이 채워진 Buffer를 얻고, releaseBuffer 로 내용을 빼낸 후 돌려준다.
  4. SurfaceFlinger
    Buffer Queue로 부터 각각 내용을 얻어서 다음과 같은 순서로 진행한다.
    4-1. 얻은 layer들을 HWC에게 넘겨서(prepare 함수), 각 Layer를 Overlay할건지 GLES composition 할지 물어본다.
    4-2. HWC가 Overlay할건지 GLES composition 할지 마킹해주면, GLES Composition 이 필요한 Layer를 GPU에 넘긴다.
    4-3. GPU에서 GLES Composition이 끝나면 합성 완료된 Layer와 Overlay로 마킹된 Layer를 모두 HWC에 넘긴다.
  5. HWC
    앞서 4-1에서 언급한 대로 각 Layer를 Overlay할건지 GLES composition 할지 결정하는 역할을 한다.
    Surface Flinger로 부터 받은 Layer들을 Overlay를 통해 하나로 합친다.
    하나로 합친 Frame을 Decon(Display Controller)에 넘긴다.
  6. Decon
    Decon은 Layer를 넘겨받은 이후에, 화면에 표시할 준비를 마치고 VSYNC를 기다린다.
    VSYNC가 들어오면, 화면을 표시하고 fense 를 통해서 Buffer 사용이 끝났으니 재사용 해도 된다고 알려준다.

Layer vs Window vs Canvas vs Texture vs Surface ?

SurfaceFlinger로 넘어오는 Buffer Queue를 사이에 놓고 위 5가지 용어가 난립한다. 혼동하기 매우 쉬우니 구분해두자.

SurfaceFlinger 분석

이제는 코드 Base로 분석을 해보자. VSYNC가 발생하면, SurfaceFlinger 내부의 ThreadLoop에 Event가 들어온다. 처리하는 Event는 두가지이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::onMessageReceived(int32_t what) {
    switch (what) {
        case MessageQueue::INVALIDATE: {
            ...
            refreshNeeded |= handleMessageInvalidate();
            if (refreshNeeded) signalRefresh();
            break;
        }
        case MessageQueue::REFRESH: {
            handleMessageRefresh();
            break;
        }
    }
}

Invalidate : App 들로부터 그려진 Layer 들을 받아서, 이전 Layer들 중 새로 그릴 것들과 아닌 것들을 구분하는 과정이다. 코드에서 알 수 있듯이 Invalidate가 수행되면 뒤에 반드시 Referesh도 같이 수행된다.

Refresh : 위에서 설명한 4-1, 4-2, 4-3이 모두 수행된다.

좀더 자세히 살펴보자.

Invalidate

switch 문에 MessageQueue::INVALIDATE 가 들어오면 handleMessageInvalidate를 거쳐 handlePageFlip를 호출한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// services/surfaceflinger/SurfaceFlinger.cpp
bool SurfaceFlinger::handleMessageInvalidate() {
    return handlePageFlip();
}

bool SurfaceFlinger::handlePageFlip()
{
    ...
    // Z order로 탐색하면서 바뀐 Layer를 찾음
    mDrawingState.traverseInZOrder([&](Layer* layer) {
        ...
        if (layer->shouldPresentNow(mPrimaryDispSync)) {
            mLayersWithQueuedFrames.push_back(layer);
        } else {
            layer->useEmptyDamage();
        }
        ...
    });

    // 바뀐 모든 Layer들의 정보를 Buffer Queue로 부터 가져오고, 바뀌었다고 표시함
    for (auto& layer : mLayersWithQueuedFrames) {
        const Region dirty(layer->latchBuffer(visibleRegions, latchTime));
        layer->useSurfaceDamage();
        invalidateLayerStack(layer, dirty);
    }
    ...
}

handlePageFlip에서는 먼저 Z order로 탐색하면서 바뀐 Layer를 찾는다. 찾은 것을 push_back(layer) 로 Queue에 밀어넣는다.

그 이후 for loop으로 바뀐 모든 Layer들에 대해 latchBuffer를 호출해 채워진 Buffer들을 Buffer Queue로 부터 가져온다.

1
2
3
4
5
6
7
8
9
// services/surfaceflinger/Layer.cpp
Region Layer::latchBuffer(bool& recomputeVisibleRegions, nsecs_t latchTime)
{
    ...
    status_t updateResult = mSurfaceFlingerConsumer->updateTexImage(&r,
        mFlinger->mPrimaryDispSync, &mAutoRefresh, &queuedBuffer,
        mLastFrameNumberReceived);
    ...
}

latchBuffer 는 무지 길지만 가장 중요한 함수는 updateTexImage이다. 이 함수가 acquire_buffer 함수를 호출해서 Buffer Queue로부터 채워진 Buffer들을 가져온다.

Refresh

Invalidate가 끝나면 이어서 handleMessageRefresh가 호출된다.

1
2
3
4
5
6
7
8
// services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::handleMessageRefresh() {
    ... 
    setUpHWComposer();
    ...
    doComposition();
    ...
}

가장 중요한 함수는 setUpHWComposerdoComposition이다.

1
2
3
4
5
6
7
8
9
// services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::setUpHWComposer() {
    // HWC 셋팅
    ...
    for (size_t displayId = 0; displayId < mDisplays.size(); ++displayId) {
        ...
        status_t result = displayDevice->prepareFrame(*mHwc);
    }
}

먼저 setUpHWComposer는 함수 전반부에서 HWC를 셋팅한다. 이후에 각 Layer를 Overlay할건지 GLES composition 할지 HWC에 물어보기 위해 Android에 연결된 모든 Display Device에 대해서 prepareFrame 를 호출해 준다.

이때 argument로 mHWC를 넘긴다. mHWC안에는 Chip Vendor사에서 제공하는 HWComposer 내부 함수의 함수포인터 *prepare가 들어있다.

*prepare 를 통해서 각 Layer를 Overlay할건지 GLES composition 할지 알았다면 doComposition를 수행한다.

1
2
3
4
5
6
7
8
9
void SurfaceFlinger::doComposition() {
    for (size_t dpy=0 ; dpy<mDisplays.size() ; dpy++) {
            ...
            doDisplayComposition(hw, dirtyRegion);
            ...
        }
    }
    postFramebuffer();
}

(1)doComposition 내의 중요한 함수로는 (2)doDisplayComposition(3)postFramebuffer가 있다. 함수 이름이 다소 혼동되게 비슷해서, 이름 앞에다가 숫자를 붙여놨다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// services/surfaceflinger/SurfaceFlinger.cpp
 void SurfaceFlinger::doDisplayComposition(
        const sp<const DisplayDevice>& displayDevice,
        const Region& inDirtyRegion)
{
    ...
    if (!doComposeSurfaces(displayDevice, dirtyRegion)) return;
    ...
    displayDevice->swapBuffers(getHwComposer());
}

bool SurfaceFlinger::doComposeSurfaces(
        const sp<const DisplayDevice>& displayDevice, const Region& dirty)
{
    ...
    if (!displayDevice->makeCurrent(mEGLDisplay, mEGLContext)) {
        ...    
    }
    ...
    for (auto& layer : displayDevice->getVisibleLayersSortedByZ()) {
        ...
        switch (layer->getCompositionType(hwcId)) {
            ...
            case HWC2::Composition::Client: {
                layer->draw(displayDevice, clip);
                break;
            }
            default: break;
        }
    }
    ...
}

(2)doDisplayComposition는 기본적으로 Frame을 GLES composition한다.

EGL과 OpenGL을 잘 안다면 이해가 더 쉬울수도 있다. (필자는 잘 모른다.^^) (2)doDisplayComposition 함수를 호출하면 그 안에서 (4)doComposeSurfaces를 부른다.

(4)doComposeSurfaces의 역할 첫 번째는 makeCurrent 함수 내부에서 eglMakeCurrent 를 불러서 인자로 넣어주는 Surface에 이제 그리겠다고 선언을 해주는 것이다. 두 번째는 GLES composition을 실제로 수행한다. 레이어 중 GLES 합성이 필요한 것에게 layer->draw(displayDevice, clip);를 호출해준다. 이 draw 안에서는 여러 GL command들이 불리면서 필요한 그림을 마구 그린다.

(4)doComposeSurfaces가 끝나면 (2)doDisplayComposition의 남은 부분에서 swapBuffers를 부른다. swapBuffers 안에서는 eglSwapBuffer가 호출되는데 이것은 그림을 다 그렸다고 알려주는 역할을 한다고 이해하면 쉽다. (사실은 더블 버퍼중 Back Buffer와 Front Buffer를 뒤바꿔놓는 역할이다.)

다시 (1)doComposition 으로 돌아가서 남은 (3)postFramebuffer를 처리하자. (3)postFramebuffer 함수는 HWC에 overlay할 layer들을 보내주는 역할을 한다. 앞서 *prepare 처럼 *set이라는 HWC 함수의 함수포인터를 가지고 있어서, 이 함수로 모든 레이어 정보들을 보낸다.

정리

글이 거지같이 복잡해졌기 때문에 정리가 필요하다고 생각한다. 함수 호출 순서는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
onMessageReceived()
  handleMessageInvalidate() // Buffer로 부터 데이터 받아오기
    handlePageFlip() // Layer들 Z-order로 돌면서 다시 받아올것 찾기
      latchBuffer()
        updateTexImage()
          acquire_buffer() // Buffer Queue에서 Buffer 받아오기
    handleMessageRefresh()
      setUpHWComposer() // HWC 셋팅하고
        prepareFrame() 
          (*prepare()) // HWC에 Overlay할건지 GLES composition 할지 묻기
      doComposition()
        doDisplayComposition()
          doComposeSurfaces()
            makeCurrent()
              eglMakeCurrent() // OpenGL에 그릴 Surface 전해주기
            layer->draw()
              GL_command() // 그리기
          swapBuffers()
            eglSwapBuffer() // 다 그렸다고 알려기
        postFramebuffer()
          (*set()) // layer 들 overlay 하기 위해 HWC로 넘기기