외부 그래픽 렌더링 탐험일지

벼리·2025년 4월 27일
1

들어가며

안드로이드 앱을 개발하는데 있어서, View나 비디오 등을 효율적으로 렌더링 하는 것은 시각적 효과와 성능을 모두 갖춘 애플리케이션을 만드는데 매우 중요합니다. 그렇다면 안드로이드에서는 어떤 렌더링 방법들을 제공하고 있을까요?

지금부터 그 방법들을 소개하겠습니다.

목표

  1. 안드로이드 기본 렌더링 방법외부 그래픽 렌더링 방법의 차이 익히기
  2. 상황에 따라 어떤 외부 그래픽 렌더링 방법을 적용해야 하는지 알아보기

사전 지식

본 글은 아래의 사전 지식을 가지고 있는 독자를 대상으로 하고 있습니다.

  • compose UI가 그려지는 과정
  • compose를 이용해 UI를 구현한 경험
  • 카메라, 비디오 등 외부 그래픽 요소를 구현한 경험

Skia: 기본 렌더링

기본적으로 뷰는 어떤 방법으로 렌더링 될까요? 안드로이드에서는 Skia를 통해 UI를 렌더링 합니다.

Skia란 C++로 작성된 오픈소스 2D 그래픽 라이브러리입니다. 정식 명칭은 Skia Graphics Engine이며 보통 줄여서 Skia라고 부릅니다. 

Skia는 서로 다른 플랫폼 간의 그래픽 API 차이를 추상화하여 통일된 인터페이스를 제공합니다. 대표적으로 Google Chrome, Mozilla Firefox, Flutter, Android 등의 프로젝트에서 널리 사용되고 있으며, Android와 Flutter의 경우 별도의 설정 없이도 기본적으로 Skia를 통해 UI가 렌더링됩니다.

외부 그래픽 요소를 렌더링하는 방법은?

Android 앱의 UI 프레임워크는 View를 최상위로 하는 객체 계층 구조를 기반으로 합니다.

Android UI 렌더링 프로세스는 다음과 같습니다.
1. Measure과 Layout 작업으로 화면상에 UI 요소들이 들어갈 정확한 위치와 크기를 결정
2. 이후 각 UI 요소를 실제로 화면에 표시하기 위해서는 렌더링 단계가 필요합니다.
3. 이때 화면에 바로 그리지 않고, 메모리 내 임시 영역에 먼저 그리게 됩니다. 이 임시 영역이 바로 버퍼(Buffer)입니다.
4. 이 버퍼는 주로 그래픽 프레임 버퍼(Frame Buffer) 형태로 관리되며, 최종적으로 Surface에 렌더링될 그래픽 데이터를 임시로 보관하는 역할을 합니다.
5. 준비된 버퍼가 완성되면, Android 시스템이 이를 화면으로 전송하여 실제로 사용자에게 보이게 됩니다.

모든 UI 요소는 Measure과 Layout 과정을 거쳐 각 요소가 영역에 적절히 들어갑니다. 이후 앱이 포그라운드에 진입했을 때 WindowManager가 설정한 Surface에 모든 가시적인 뷰 객체들이 렌더링됩니다. 그리고 앱의 UI 스레드는 매 프레임마다 버퍼와 레이아웃과 렌더링 작업을 수행합니다.

그렇다면, 외부 그래픽 요소는 어떻게 Android 그래픽과 상호작용하여 UI를 그릴 수 있을까요?

지금부터 그 방법들을 알아보고자 합니다.

SurefaceView

Video나 Camera와 같은 외부 그래픽 소스의 렌더링 결과를 화면에 표시해야 하는 상황을 가정해봅시다. 이때, Video를 렌더링 할려면 매초 30~60장의 프레임을 디코딩해야 합니다. 카메라의 경우 실시간으로 센서로부터 이미지를 받아와 처리해야 합니다. 이 과정을 거친 후, 매번 Surface에 그리고 업데이트하는 작업을 수행해야 합니다. 그렇기에 이 모든 작업을 UI 스레드에서 진행한다면 과부화가 걸릴 수 있습니다. 이를 방지하기 위해 Android 프레임워크에서는 SurfaceView라는 UI 컴포넌트를 제공하고 있습니다.

SurfaceView란? View 계층 구조 안에 별도의 composite 레이어를 삽입할 수 있도록 해주는 컴포넌트입니다. UI 스레드가 아닌 별도의 Surface를 렌더링하며, SurfaceFlinger가 하드웨어 오버레이로 별도 레이어를 합성하기 때문에 외부 그래픽을 렌더링함에 있어서 우수한 성능을 보여줍니다.

외부 버퍼 소스를 렌더링하는 경우, 이 버퍼를 화면에 표시하려면 버퍼 소스에서 디스플레이 버퍼로 내용을 복사해야합니다.SurfaceView 가 이 작업을 수행하기 때문에 외부 버퍼 소스의 렌더링 결과물을 화면에 표시할 수 있습니다. SurfaceView 를 통해 렌더링한 후, UI 스레드는 액티비티 생명주기와 조율하면서 필요에 따라 View의 크기나 위치를 조정합니다. 이후 최종적으로 Hardware Composer가 앱의 UI와 다른 레이어를 혼합하여 화면에 보여줍니다.

SurfaceView 를 통한 렌더링은 별도의 Surface에 직접 렌더링하는 것이 유리한 경우에 적합합니다. 예를 들어서 카메라 API나 OpenGL ES 컨텍스트를 사용할 때 SurfaceView가 유용합니다. SurfaceView 를 쓰지 않는다면 버퍼가 먼저 오프스크린 Surface에 합성되고, 다시 화면으로 합성되는 추가 작업이 필요합니다. 즉, SurfaceView 를 쓰면 이런 추가 작업을 없앨 수 있기 때문에 효율적입니다.

TextureView

하지만 SurfaceView는 내부적으로 별도의 오버레이 윈도우를 생성하기 때문에 WindowManager와 SurfaceFlinger 간 IPC(Inter-Process Communication)가 발생하여 리소스가 소모됩니다.

또한 SurfaceView 는 다른 뷰와 동일한 레이아웃 파라미터를 사용하되, 렌더링 결과가 투명하게 합성될 수 있는 별도의 레이어를 제공합니다. 그렇기 때문에 회전, 투명도 등의 그래픽 후처리 기능 처리하는데 제약이 있습니다. 이렇게 그래픽 후처리가 필요한 경우에 TextureView 를 사용하여 렌더링 할 수 있습니다.

TextureView 란? View 계층의 구성 요소 중 하나로, 뷰 계층에 직접 GPU 합성해 UI를 렌더링하는 컴포넌트입니다. 내부에 GL(OpenGL ES)용 외부 텍스처(SurfaceTexture)를 가지고 있기 때문에 바로 셰이더에 바인딩해서 그래픽 후처리 효과를 실시간으로 적용할 수 있다는 장점이 있습니다.

또한 SurfaceView와는 달리 별도 레이어로 빠져나가지 않고, 앱의 뷰 계층 안에서 다른 뷰들과 함께 GPU로 한 번에 합성됩니다. 그렇기 때문에 SurfaceFlinger → 앱 UI → SurfaceFliger 두 단계로 합성하지 않고, 앱 내부에서 한 번만 합성한 뒤 최종적으로 OS 레이어에 전달되어 오버헤드가 줄어듭니다.

결론

그렇다면, 외부 그래픽 렌더링 시 어떤 것을 사용해야 할까요?

그래픽 성능이나, DRM 보호가 중요하다면 SurfaceView를 선택하는 것이 좋습니다. 반면, 그래픽 후처리를 할 수 없다는 단점을 인지하고 있어야 합니다. 단, 오버헤드를 줄이거나, 그래픽 후처리가 중요하다면 TextureView가 중요합니다.

이렇듯 각각의 장단점과 기능 요구사항을 파악하여 적절한 뷰를 선택하는 것이 중요합니다.

profile
코딩일기

0개의 댓글