이전 포스트들에서 SurfaceTexture
, Surface
, MediaCodec
등 미디어, 그래픽에 쓰이는 API들에 대해 다루었습니다.. 이러한 API들의 역할과 사용 방법에 대해서는 다루었지만, 작동 방식에 대해 조금 더 깊이 알아보고 싶어서 Google I/O ‘18의 Drawn out: How Android renders 라는 주제의 발표와 공식 문서의 내용들을 조합하여 정리하려 합니다.
이번 포스트에서 다루는 내용들은 개발자가 직접 조작이나 설정해야 하는 부분은 아니지만, 그래픽이 어떤 과정을 거쳐 화면에 표시되는지 이해하는 것에 도움이 될 것입니다.
(출처: AOSP 그래픽)
BufferQueue
BufferQueue는 이름 그대로 (그래픽) 버퍼의 큐입니다. 버퍼의 수는 생성할 때 결정할 수 있습니다.
중요한 부분은 BufferQueue
는 두 개의 엔드포인트를 가지는데, 생산자와 소비자라고 합니다.
(출처: AOSP 그래픽)
생산자는 dequeueBuffer()
를 호출하여 BufferQueue
에서 버퍼를 가져와 소유권을 얻습니다. 그 후 생산자 라는 이름에 맞게 내용을 채워넣을 수 있습니다. 그게 Canvas
가 될 수도 있고, OpenGL ES
가 될 수도 있으며, 픽셀 데이터를 직접 넣을 수도 있습니다.
생산자가 버퍼에 데이터를 모두 넣은 후에는, 다시 BufferQueue
에 버퍼를 queueBuffer()
호출로 돌려줍니다. SurfaceTexture
클래스에서 OpenGL ES 렌더링이 끝난 후 eglSwapBuffers()
를 호출하는 것도 이 queueBuffer()
과정에 해당합니다.
생산자의 역할은 끝났으니, 이제는 소비자 차례입니다. 소비자는 acquireBuffer()
를 호출하여 BufferQueue
에서 버퍼를 가져옵니다. 소비자는 가져온 버퍼로 원하는 작업을 수행하고(지난 포스트의 MediaCodec
을 예로 들자면, 버퍼를 가져와서 MediaMuxer에 작성하였습니다.) releaseBuffer()
로 다시 BufferQueue
에 버퍼를 돌려줍니다.
이렇게 생산자와 소비자가 그래픽 버퍼를 BufferQueue
를 통해 교환할 수 있기 때문에, 생산자와 소비자가 다른 성격의 API이더라도 그래픽 데이터를 전달할 수 있습니다. 따라서 이 앱에서 동영상을 재생하는 MediaPlayer
로부터 OpenGL ES를 사용하는 SurfaceTexture
에 프레임 단위로 동영상을 전달하는 것이 가능했던 것이죠.
BufferQueue
는 소비자에 해당하는 컴포넌트에서 생성하여, 나머지 하나의 엔드포인트에 해당하는 생산자에게 Surface를 넘겨주어 버퍼를 채우도록 합니다.
이전에 다뤘던 Surface
는 BufferQueue
의 생산자쪽 엔드포인트에 전달하여 그래픽 버퍼를 가져오기 위해 사용된 것입니다. 이 엔드포인트의 반대편, 즉 소비자는 주로 SurfaceFlinger
로, Window
들의 그래픽 버퍼를 각각 BufferQueue로 모아 화면을 구성하는 역할을 합니다. 또한 제가 이전 포스트들에서 다룬 SurfaceTexture
, MediaCodec
API도 BufferQueue의 소비자 엔드포인트로 동작할 수 있습니다. SurfaceFlinger
를 소비자로 사용하게 되는 방법 중 하나는 SurfaceView
API를 사용하는 것인데, 이는 다음 포스트에서 그림이 그려진 동영상을 재생하는 내용을 다루면서 작성하도록 하겠습니다.
WindowManager
SurfaceFlinger
를 소비자로 하는 생산자는 WindowManager
가 있습니다.
SurfaceFlinger는 버퍼를 WindowManager로부터 받아 합성하여 새로운 버퍼를 디스플레이로 보냅니다.
WindowManager는 SurfaceFlinger가 Surface를 디스플레이에 합성하는데 사용하는 버퍼와 Window의 메타데이터를 SurfaceFlinger에 제공합니다.
우리가 Dialog, Toast, Activity와 같은 요소들을 만들 때, Window
객체가 생성됩니다. 그리고 SurfaceFlinger 쪽에도 Window에 해당하는 객체가 있는데, 이를 Layer
라고 합니다.
Layer
는 소비자의 역할로 BufferQueue
를 생성하고 소유합니다. 그리고 Surface
를 생산자에 해당하는 API들에게 전달하여 그래픽 버퍼들을 SurfaceFlinger
로 가져올 수 있게 됩니다.
SurfaceFlinger
(출처: AOSP 그래픽)
SurfaceFlinger
는 이와 같이 Layer
로부터 그래픽 버퍼를 가져옵니다. 이러한 Layer는 하나가 아닌 여러 개이고, 여러 소스로부터 그래픽 버퍼를 가져옵니다.
위 그림처럼 홈 화면이나 상태 표시줄, 시스템 UI 등은 각각의 BufferQueue
를 통해 따로 그래픽 버퍼가 들어오게 됩니다.
생산자들은 그래픽 버퍼에 데이터를 계속 입력할 수 있지만, 소비자인 SurfaceFlinger
는 BufferQueue
에서 그래픽 버퍼를 항상 가져오지 않습니다. 디스플레이 새로고침 사이에서만 버퍼를 가져옵니다. 이 새로고침 주기는 일반적으로 초당 60번, 즉 60fps로 동작하고 1프레임은 약 16ms동안 지속됩니다.
이렇게 새로운 버퍼를 탐색할 수 있도록 새로고침 신호를 주는 것을 VSYNC
라고 합니다.
SurfaceFlinger
는 (60fps로 가정하고)16ms라는 짧은 시간 내에 Layer
목록에서 새로운 버퍼를 찾아 갱신하고, 새 버퍼가 없다면 기존의 버퍼를 계속 사용합니다.
그래픽 버퍼를 모두 수집한 후에는 하드웨어 컴포저(HWC)에 합성 유형을 묻고, SurfaceFlinger가 이에 해당하는 Layer
를 합성한 후, 출력 버퍼를 하드웨어 컴포저에 전달합니다.
이 이후 구성은 하드웨어에 가까운 내용이기도 하고, 저의 현재 지식 수준으로는 이해하기 어려운 내용이 많아 여기까지 작성하겠습니다. 이후 내용은 하드웨어 추상 계층(HAL)을 통해 디스플레이 하드웨어 OEM에 의해 수행됩니다.
(참고자료: SurfaceFlinger 및 WindowManager)