렌더링 되는 View의 내부를 살펴보자

더 나은 이해를 위해 이전 포스팅인 안드로이드 View가 렌더링 되는 과정을 먼저 참조할 수 있다.

렌더링하는 동안 사용되는 컴포넌트, 디스플레이 파이프 라인 및 UI와 하드웨어간 동기화가 발생하는 방식 등 더 자세한 내용을 파악하기 위해 다음과 같은 내용들을 알아야한다.

UI스레드

모든 앱은 UI 스레드에서 View를 그리게 된다. 오직 UI 스레드만이 View에 접근할 수 있다. (백그라운드 스레드에서 텍스트나, 이미지를 변경하려다가 크래시가 발생했던 경험들이 다 있을 것이다.)

간단히 말해 UI스레드의 역할은 렌더 스레드(Render Thread)에서 실행될 명령어 파이프라인을 준비한다. 엄밀히 말해 UI스레드는 뷰의 생명주기를 처리하여 렌더링에 필요한 정보를 생성한다. 이것은 UI스레드가 뷰의 onMeasure(), onLayout(), onDraw()와 같은 콜백을 실행하고 정보를 수집하여 렌더 스레드로 전달되는 것을 말한다.

렌더 스레드(Render Thread)

렌더 스레드는 롤리팝에서 도입된 컴포넌트로 UI스레드에서 입력을 받아 GPU로 처리한다. 일반적으로는 UI스레드와 렌더 스레드가 순차적으로 작동하지만, 일부 애니메이션 및 기타 항목에서는 비동기로 작동하게 된다. 이전에는 액티비티 전환과 같이 무거운 작업을 수행하는 동안 뷰 속성을 부드럽게 애니메이션 하는 것이 사실상 불가능 했지만, 롤리팝부터는 이러한 애니메이션 및 물결(Ripple)효과가 자연스럽게 적용된다. 이러한 마법같은 일은 모두 RenderThread 덕분이다.

Choreographer(코레오그래퍼)

Choreographer는 하위 계층의 디스플레이 시스템과 애플리케이션 계층의 뷰 시스템간의 다리역할을 하는 클래스이다. Choreographer는 디스플레이 서브 시스템에서 수직 동기화와 같은 타이밍 펄스를 수신 한 후 다음 디스플레이 프레임 렌더링의 일부로 작업이 발생하도록 스케줄한다.

애플리케이션은 일반적으로 애니메이션 프레임워크 또는 뷰 계층에서 더 높은 수준의 추상화를 사용하여 Choreographer와 간접적으로 상호 작용한다.

다음은 고급 API를 사용하여 수행 할 수있는 몇 가지 예제가 있다.

  • 디스플레이 프레임 렌더링과 동기화된 애니메이션을 게시하려 ValueAnimator.start()를 사용한다.
  • 다음 프레임의 시작 부분에서 Runnable 한 번 호출하기 위해서 View.postOnAnimation()을 사용한다.
  • 지연 후 다음 프레임의 시작 부분에서 Runnable을 한 번 호출 하려면 View.postOnAnimationDelayed()를 사용한다.
  • 다음 디스플레이 프레임의 시작 부분에서 View.invalidate() 호출하도록 하려면 View.postInvalidateOnAnimation () 또는 View.postInvalidateOnAnimation (int, int, int, int)을 사용한다.
  • 디스플레이 프레임 렌더링과 동기화 되어 뷰의 내용이 부드럽게 스크롤되게 그리는것은 View.onDraw에서 이미 적절하게 자동으로 처리가 되고 있다. 

각 루퍼 스레드에는 자체 Choreographer가 있다. 다른 스레드는 Choreographer에서 실행할 콜백을 게시 할 수 있지만 Choreographer가 속한 루퍼에서 실행된다.

V-Sync(수직 동기화)

V-Sync(Vertical Synchronization)를 알기 위해서는 리프레시 레이트(Refresh rate)와 프레임 레이트(Frame rate) 두 용어에 대해 알아야한다. 리프레시 레이트는 화면 주사율이 초당 몇 번씩 화면을 업데이트 할 수 있는지를 나타내고, 프레임 레이트는 GPU가 초당 프레임을 그릴 수 있는 횟수를 의미한다. 둘 다 이미지를 화면에 올바르게 그리려면 함께 작동해야 한다. 이 둘이 동일한 빈도로 발생한다는 보장은 없다. 동일한 빈도로 발생하지 않는 경우 눈에 띌만한 불완전한 프레임이 화면에 그려지게 된다.

GPU에서 사용하는 버퍼로 백 버퍼(Back buffer)와 프레임 버퍼(Frame buffer)가 있다. 백 버퍼는 프레임을 그리는 데 사용된다. 백버퍼에서 작업이 완료되면 GPU가 해당 프레임을 프레임 버퍼에 복사한다. 간단히 말해, 프레임 버퍼는 화면에 렌더링해야하는 데이터를 보유하게 된다. 그러나 콘텐츠를 백버퍼에서 프레임 버퍼로 복사하는 동안 프레임 버퍼를 참조하게 되면 문제가 된다. 다음 그림을 참조하자

이러한 문제를 막기 위해 V-Sync가 필요하다. V-Sync는 화면이 새로 고쳐질 경우 백버퍼에서 프레임 버퍼로의 복사 작업을 유지하게 되고, 프레임 버퍼에 완전히 그려진 프레임은 화면에 렌더링된다. 안드로이드 스크린은 보통 초당 60회(60fps)를 화면에 그리는데, 1프레임을 그리기 위한 준비시간이 약16.66ms라는 뜻이다. 

 

 

SurfaceFlinger

SurfaceFlinger의 역할은 입력된 버퍼들을 합성하여 디스플레이로 보내는 역할을 한다.

SurfaceFlinger는 두가지 방법으로 버퍼를 처리하는데 BufferQueue 및 SurfaceControl 또는 ASurfaceControl을 받는 방법이다.

BufferQueue 및 SurfaceControl을 사용할 때는 앱이 포어그라운드상태이면,  WindowManager 에서 버퍼를 요청하고, 그런 다음 WindowManager는 SurfaceFlinger에서 레이어를 요청한다. 레이어는 BufferQueue를 포함하는 Surface와 디스플레이 프레임과 같은 레이어 메타 데이터를 포함하는 SurfaceControl의 조합이다. SurfaceFlinger는 레이어를 만들어 WindowManager로 보낸뒤 WindowManager는 Surface를 앱으로 보내지만 SurfaceControl가 화면에서 앱의 외관을 조작하도록 한다.

Android 10에서는 ASurfaceControl이 추가되어 SurfaceFlinger가 버퍼를 받는 방법이 추가되었다. ASurfaceControl은 Surface와 SurfaceControl을 SurfaceFlinger로 전송되는 하나의 트랜잭션 패키지로 결합한다. ASurfaceControl은 ASurfaceTransactions를 통해 앱이 업데이트하는 계층과 연결되고, 앱은 래치 시간(latch time), 획득 시간(acquire times) 등과 같은 정보가 포함된 ASurfaceTransactionStats를 전달하는 콜백을 통해 ASurfaceTransactions에 대한 정보를 얻게 된다.

다음 표에는 ASurfaceControl 및 관련 구성 요소에 대한 자세한 내용이 포함되어 있다.

Component Description
ASurfaceControl SurfaceControl을 래핑하고 앱이 디스플레이의 레이어에 해당하는 SurfaceControl을 만들 수 있도록 한다. ANativeWindow의 서브클래스 또는  ASurfaceControl의 또 다른 서브클래스로 만들 수 있다.
ASurfaceTransaction 클라이언트가 지오메트리와 같은 레이어의 설명 속성을 편집 할 수 있도록 트랜잭션을 래핑하고 업데이트 된 버퍼를 SurfaceFlinger로 보낸다.
ASurfaceTransactionStats 사전 등록 된 콜백을 통해 래치 시간, 획득 시간 및 이전 릴리스 펜스와 같은 제시된 트랜잭션에 대한 정보를 앱에 보낸다.

앱에서 언제든지 버퍼를 내보낼수 있지만 SurfaceFlinger는 장치에 따라 다를 수 있는 디스플레이 갱신 주기 사이의 버퍼만 허용하도록 깨어난다. 이렇게하면 메모리 사용이 최소화 되고 화면이 새로 고쳐질 때 발생할 수있는 화면의 불완전성을 피할 수 있다.

디스플레이가 갱신 주기 사이에 있으면 디스플레이는 V-Sync신호를 SurfaceFlinger로 보내게 되고  V-Sync 신호로 인해 온전한 상태의 화면을 보게 된다.

SurfaceFlinger가 새로운 버퍼를 찾으면 새로운 버퍼를 획득하고, 그렇지 않은 경우 SurfaceFlinger는 이전에 획득한 버퍼를 계속 사용한다. SurfaceFlinger는 항상 무언가를 표시해야하므로 하나의 버퍼를 꼭 쥐고 있는다. 레이어에 버퍼가 제출되지 않은 경우 레이어는 무시된다.

SurfaceFlinger는 가시적인 레이어에 대한 모든 버퍼를 수집 한 후 하드웨어 컴포저 (HWC)에게 어떤식으로 합성할 것인지를 묻고, 하드웨어 컴포저가 레이어 컴포지션 타입을 클라이언트 컴포지션으로 설정하면 SurfaceFlinger가 해당 레이어를 합성한다. 그런 다음 SurfaceFlinger는 출력 버퍼를 하드웨어 컴포저로 전달한다.

BufferQueue

버퍼는 단순히 다른 장소로 이동하는 동안 무언가를 일시적으로 저장하기 위해 할당 된 메모리 영역이다. 버퍼 큐는 그래픽 버퍼가 존재하는 큐인데, 일반적으로 1 ~ 3 개의 버퍼가 있다. 버퍼큐에는 주로 두 개의 유형이(endpoint) 있다. 하나는 생산자(Producer)이고 다른 하나는 소비자(Consumer)이다.

1단계 : 생산자가 큐에서 dequeueBuffer()를 호출하면 버퍼 상태가 DEQUEUED가 되고, 이제 버퍼의 소유자는 생산자이며 OpenGL 또는 Canvas를 사용하여 렌더링 등을 통해 버퍼를 채우게 된다.

2단계 : 생산자가 버퍼를 채운 후에는 버퍼가 BufferQueue로 다시 큐잉되도록 queueBuffer()를 호출한다. 이때 버퍼 상태는 DEQUEUED에서 QUEUED로 변경되고 버퍼의 소유자는 BufferQueue가 된다.

3단계 : 이제 소비자는 BufferQueue에서 acquireBuffer()를 호출하고 큐에서 사용 가능한 버퍼를 가져온다. 이제 소비자가 버퍼의 소유자가 되고, 버퍼 상태는 QUEUED에서 ACQUIRED로 변경된다.

4단계 : 소비자에서 버퍼를 이용한 작업을 마친 후 release()를 호출하여 버퍼가 다시 BufferQueue로 돌아갈 수 있게 한다. 이때 버퍼 상태는 ACQUIRED에서 FREE로 변경되고 버퍼 소유자는 BufferQueue가 된다.

마지막으로 BufferQueue의 상태

WindowManager가 생산자이고, SurfaceFlinger가 소비자 인 예를 살펴 보자.
Dialog를 만들거나 Activity를 만들면 Window는 WindowManager에 연결된다. 이제 Window와 동일 선상의 형제격으로 SurfaceFlinger 쪽의 Layer가 존재한다. Layer는 시스템에서 응용 프로그램의 BufferQueue를 생성하고 소유하는 컴포넌트이며, Surface는 BufferQueue와 상호 작용할 생산자의 엔드포인트 API이다.

Display Pipeline(디스플레이 파이프라인)

V-Sync 신호는 디스플레이 파이프 라인을 동기화 한다. 디스플레이 파이프라인은 앱 렌더링과 SurfaceFlinger 컴포지션 및 디스플레이에 이미지를 표시하는 Hardware Composer (HWC)로 구성된다. V-Sync 는 앱의 렌더링 시작 시간, SurfaceFlinger의 화면 합성 시간 및 디스플레이 리프레시 레이트를 동기화한다. 이 동기화는 버벅거림을 없애고 그래픽의 시각적 성능을 향상시키게 된다.

HWC는 V-Sync 이벤트를 생성하고 콜백과 함께 이벤트를 SurfaceFlinger로 보내게 된다.

SurfaceView

SurfaceView는 뷰 계층 구조에 포함된 전용 Surface를 제공한다. 이 Surface의 형식과 원하는 경우 크기를 제어 할 수 있다. SurfaceView는 화면의 올바른 위치에 Surface를 배치하게 된다.

SurfaceView는 Window가 Surface 인 것처럼 동작한다. 그런 다음 Surface의 구멍을 정확하게 자르고, WindowManager와 SurfaceFlinger에게 두 번째 Surface를 만들도록 요청한다. 그런 뒤 첫 번째 Surface 아래로 밀어 넣는다. 다음과 같이 서로 다른 두 개의 Surface와 BufferQueue가 존재하게 된다.

 

 

참조:

https://academy.realm.io/kr/posts/aw205-android-soft-keyboard-tools-namespace-render-thread/

https://developer.android.com/reference/android/view/Choreographer

https://medium.com/@workingkills/understanding-the-renderthread-4dc17bcaf979

https://medium.com/better-programming/android-internals-for-rendering-a-view-430cd394e225

https://source.android.com/devices/graphics/surfaceflinger-windowmanager

https://source.android.com/devices/graphics/arch-bq-gralloc

 

카테고리: Graphics

1개의 댓글

성빈 · 2022년 5월 18일 2:35 오전

와우… 아직 제가 이해하기엔 너무 어려운 영역이네요 ㅠ
좋은 글 감사합니다! ㅎㅎ

답글 남기기

Avatar placeholder

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.