4 minute read

해당 글은 이전글과 이어지는 내용으로 이전글을 참고하시면 이해하는데 도움이 될 것입니다.

해당 시리즈는 실제 깊은 코드 수준보단 간단한 이론을 바탕으로 글을 작성했기 때문에, 내용에 구현방식이 존재하지 않거나 누락된 내용이 존재할 수 있습니다. 하지만 Android 그래픽 파이프라인에 대한 일부를 이해할 수 있을 것입니다.

SurfaceFlinger

SurfaceFlinger는 Android내 화면을 어떻게 그릴지에 대해 기여하는 컴포넌트로 여러 앱과 시스템 UI가 생성한 그래픽 버퍼들을 받아서 하나의 프레임으로 합성하고 디스플레이 하드웨어에 전달해주는 역할을 합니다.

이전 포스트에서 Surface에 구조에 대해 설명하면서 Producer와 Consumer의 의미에 대한 언급을 하였는데, 여기서 SurfaceFlinger는 Producer가 생산한 그래픽 버퍼 데이터를 소비하는 Consumer의 역할을 수행합니다. SurfaceFlinger에 대해 자세하게 설명하기전 몇 가지의 용어를 정리하고 내용을 이어나가도록 하겠습니다.

WindowManagerService

SurfaceFlinger에 대한 구조나 동작방식을 자세하게 알기 위해선 먼저 WindowManagerService에 대해 이해해야합니다.

WindowManagerService는 화면에 UI 컴포넌트들을 어떻게 보여줄지와 어떤 순서로 겹쳐질지를 결정하고 조율하는 역할을 합니다. 이때 여기서 관리하는 UI 컴포넌트를 Window라고 합니다. WindowManagerService는 또한 터치나 키보드 입력이 어디로 전달되어야하고 어떤 Window가 현재 포커스를 가지고 있어야 하는지와 같은 인터렉션 관리 역할을 수행합니다.

우리가 알고있는 WindowManager라는 용어는 이런 WindowManagerService와 상호작용하는 Android의 인터페이스를 의미합니다.

SurfaceControl

SurfaceControl은 Android에서 그래픽 레이어의 시각적 속성을 제어하고 관리하는 데 사용되는 객체로 특정 Surface가 화면에 어떻게 보여질지에 대한 시각적 속성들을 포함하고 제어합니다. 해당 요소들의 예시는 대표적으로 위치, 크기, z-order, 투명도등이 존재합니다.

SurfaceControl의 속성 변경은 원자적으로 동작하며, WindowManagerService 같은 컴포넌트에 의해 SurfaceControl의 속성 변경을 수행합니다. 이러한 모든 속성들은 추후 SurfaceFlinger가 해당 Surface를 다른 Surface들과 합성할 때 어떻게 처리해야 하는지에 대한 정보가 됩니다.

레이어(layer)

레이어는 SurfaceSurfaceControl의 조합으로 Surface가 보유한 BufferQueue와 위치,사이즈, Z-order같은 메타데이터를 포함하고 있습니다.

SurfaceFlinger의 동작 방식

SurfaceFlinger는 크게 Surface 생성과 합성의 역할을 수행합니다. 아래에서부터는 SurfaceFlinger의 2가지역할을 중점으로 SurfaceFlinger 동작 방식에 대한 내용을 풀어나가겠습니다.

Surface 생성

이전 포스트에서 Surface와 BufferQueue에 대한 부분을 다루었지만, 근본적으로 Surface가 어떻게 생성되고 어떻게 생산자와 소비자에게 연결되는지에 대한 내용은 언급되지 않았습니다. Surface가 생성되는 방식은 여러가지가 있지만, 그 중 화면을 표시하기 위해 생성되는 SurfaceWindowManagerServiceSurfaceflinger의 상호작용에 의해 생성됩니다.

먼저 애플리케이션은 무엇인가를 화면에 그리려고 할 때 가장 먼저 WindowManger.addView() 같은 메서드를 호출하여 WindowManager에게 UI를 보여줄 Window를 생성해 달라 요청합니다. 해당 요청에는 Window의 타입, 크기, 위치, 그리고 Z-Order등의 정보가 포함됩니다. 요청을 전달받은 WindowManagerService는 주어진 바탕으로 Window객체를 생성합니다.

하지만 아직 생성된 Window에는 프레임을 그릴 수 있는 방법이 존재하지 않습니다. Window에 그림을 그릴 수 있도록 WindowManagerServiceWindow의 정보를 포함하여 SurfaceFlinger에게 새로운 Surface를 생성해달라고 요청을 전달합니다. 이때 해당 요청은 SurfaceControl이라는 객체를 통해 이루어지게 됩니다.

SurfaceFlingerServiceControl을 통해 전달된 정보을 받아서 새로운 레이어 객체와 연결된 Surface 객체를 생성합니다. 이때의 Surface는 내부적으로 BufferQueue와 연결됩니다. 그 후 SurfaceFlinger는 생성된 Surface에 대한 정보를 다시 WindowManagerService로 전달하는 과정을 끝으로 각 컴포넌트 간의 연결이 마무리 됩니다.

합성

SurfaceFlinger는 BufferQueue의 Consumer 역할로서 그래픽 버퍼를 획득할 수 있습니다. 하지만 SurfaceFlinger는 오로지 하나의 그래픽 버퍼만을 소비하는것이 아닙니다. 일반적으로 디바이스는 시스템 바와 액티비티에서 노출되는 화면 그리고 토스트 메시지등 Window를 보유한 컴포넌트들이 존재하는데, 해당 컴포넌들의 Window는 모두 각자의 Surface를 보유하고 버퍼를 생산해내는 역할을 수행합니다. 이때 SurfaceFlinger는 여러 Surface에서 생산되는 그래픽 버퍼의 데이터를 적절하게 조합하여 디스플레이 하드웨어에 넘겨주어야만 합니다. 이러한 과정을 합성이라고 표현합니다.

합성의 방식은 단순한 병합이 아닌, 각 레이어의 위치, 크기, 투명도들을 고려하여 적절한 화면으로 구성해야합니다. 사전에 SurfaceFlingerWindowManagerService와의 협력을 통해 각 화면 컴포넌트들에 대한 독립적인 레이어 정보를 가지고 있습니다. 이러한 상태에서 SurfaceFlinger는 특정 신호가 올 때마다 자신이 관리하는 모든 레이어의 Surface에서 최신 그래픽 버퍼를 가져옵니다. 가져온 레이어들의 그래픽 버퍼들은 Z-Order에 맞춰 순서대로 쌓아 올리고 위치 및 투명도 등의 속성을 고려하여 픽셀들을 배치 및 블렌딩하는 작업을 수행합니다. 이렇게 최종적으로 합성된 단일 이미지를 디스플레이로 보냅니다.

HWC(Hardware Composer)

HWC는 Hardware Composer의 약자로 Android 그래픽 시스템에서 디스플레이 합성을 수행하는 하드웨어 모듈로 최신 출시되는 대부분의 안드로이드 기기에 탑재되어 있습니다. 디스플레이 합성에 있어서 HWC를 통한 처리 방식은 소프트웨어 방식보다 전력적인 측면에서 효율적이며, 지연과 버벅임이 없는 UI를 만들어 낼 수 있습니다. 이러한 이유로 SurfaceFlinger는 화면을 업데이트해야 할 때 자신이 가지고 있는 모든 레이어 정보를 HWC에게 최대한 전달하여 합성 연산을 위임합니다.

HWC는 대부분의 레이어에 대한 합성 처리를 자신이 직접 수행하려고 하지만 일부 복잡해서 하드웨어적으로 처리할 수 없는 레이어의 경우 SurfaceFlinger에게 해당 레이어의 합성을 맡깁니다. SurfaceFlinger는 이를 GPU를 사용하여 처리하며, 최종적으로 처리한 결과는 SurfaceFlinger 또는 HWC 가 나머지 레이어들과 함께 통합하여 디스플레이에 출력합니다. HWC가 잘 처리할 수 있는 레이어는 단순한 배경화면과 같이 사각형 형태로이루어진 불투명한 영역들입니다. 반면 블렌딩이나 특정 효과가 있는 들어있는 레이어들은 SurfaceFlinger가 GPU를 통해 처리를 합니다.

이제까지의 내용들을 정리하자면 SurfaceFlingerHWC 모두 레이어를 합성할 수 있지만, 효율성 적인 측면에서 합성 연산을 HWC에게 대부분을 위임하고 일부 복잡한 레이어는 SurfaceFlinger가 처리하여 최종적으로 디스플레이에 출력되는 방식으로 수행됩니다.

안드로이드 개발자 옵션에는 하드웨어 레이어 업데이트 표시라는 항목이 존재합니다. 이는 현재 HWC가 어떤 레이어를 직접 처리하고 있는지 시각적으로 확인해볼 수 있습니다.

VSync(Vertical Synchronization, 수직 동기화)

SurfaceFlinger는 어떤 타이밍에 합성을 수행할까에 대한 부분은 Vsync 메커니즘과 연관이 되어있습니다. 안드로이드의 기본적인 렌더링 방식은 VSync를 통해 동기화하는 과정으로 진행됩니다. Android 시스템이 VSync 신호에 맞춰 프레임을 그릴 준비를 한다면, SurfaceFlinger또한 VSync 신호에 맞춰 여러 레이어들을 합성하여 디스플레이로 전달합니다.

여기서 VSync는 수직 동기화라고 불리며, Vertical Synchronization의 약자입니다. 컴퓨터 그래픽스에서는 Screen Tearing이라는 현상이 발생할 수 있는데, Vsync는 이러한 현상을 방지하고 부드러운 화면 출력을 도와주는 역할을 합니다. VSync는 화면 모니터의 재생률에 맞춰 GPU의 렌더링 속도를 동기화하는 기술로 렌더링 속도가 빠르더라도 모니터 재생률에 맞춰 고정되지만, 안정적으로 프레임을 제공할 수 있다는 특징이 존재합니다.

Screen Tearing은 디스플레이에서 발생되는 문제중 하나로 화면이 찢어진 것처럼 보이는 현상을 의미합니다. 해당 현상은 GPU의 프레임 생성속도와 모니터의 화면 재생률이 동기화되지 않았을 때 발생합니다. 대부분의 모니터는 화면을 위에서 아래로 순차적으로 그리는 방식으로 이미지를 표시합니다. 예를 들어, 프론트 버퍼의 그림이 디스플레이에 출력되는 동안 백 버퍼에 새로운 프레임이 빠르게 생성되고 그, 프레임을 프론트 버퍼로 넘겨버리게 될 경우 도중에 프레임이 바뀌게 되는 것이기 때문에 화면 상단과 하단의 다른 이미지를 동시에 표시하게 됩니다. 해당 문제에 대한 자세한 내용은 [해당 링크]를 참고하시면 좋습니다.



1편에 이어 지금까지 SurfaceSurfaceFlinger를 중심으로 Android 화면이 어떻게 구성되고 표시되는지 살펴보았습니다. 애플리케이션을 통해 생성된 그래픽 데이터가 여러 컴포넌트간에 상호 작용하여 디스플레이 출력하는 복잡한 과정을 정리하였는데, Android 그래픽 렌더링 파이프라인이 어떻게 구성되는지 간략하게나마 이해하는데 도움이 되었을 것이라 생각합니다.