Android/OpenGL ES

0) Android Graphics architecture

show2888 2021. 9. 28. 15:50
반응형

OpenGL을 시작하기전에 안드로이드의 그래픽구조와 요소들을 먼저 파악한다.

 

이글은 안드로이드 공식 문서와 'Tech Writing의 끝장' 블로그의 글을 참고 하였습니다.

1. BufferQueue 및 Gralloc

안드로이드에서 모든 그래픽 요소들의 중심에는 BufferQueue라는 클래스가 있다.

그래픽 데이터 버퍼들을 생성하는 컴포넌트 ("생산자")와 이 데이터를 받아서 디스플레이 하거나 추가 프로세싱을 하려는 컴포넌트 ("소비자")를 연결시켜주는 것이다. 생산자와 소비자는 서로 다른 프로세스에 존재할 수도 있다. 시스템 전반에 걸쳐 그래픽 데이터 버퍼를 이동시키는 거의 모든 작업들이 BufferQueue를 통해 처리된다.

 

dequeueBuffer()를 호출하여 BufferQueue에서 프리 버퍼를 요청하는 방식으로 버퍼의 너비, 높이, 픽셀 형식과 사용 플래그를 지정합니다. 이어서 생산자는 queueBuffer()를 호출하여 버퍼를 채우고 버퍼를 대기열에 반환합니다. 다음으로 소비자는 acquireBuffer()로 버퍼를 획득하고 버퍼 콘텐츠를 활용한다. 이를 완료한 소비자는 releaseBuffer()를 호출하여 버퍼를 대기열에 반환한다. sync 프레임워크는 버퍼가 Android 그래픽 파이프라인을 통해 이동하는 방식을 제어한다.

 

 Systrace를 활용하여 BufferQueue를 추적 할 수 있다.

 

실제 버퍼 할당은 "gralloc" 라는 메모리 할당자에 의해 처리된다.

이러한 할당된 버퍼의 특성들은 용도 플래그 인자에 의해 결정되며, 다음과 같은 특성들을 포함하고 있다. 
  • Software(CPU)에서 얼마나 자주 메모리를 액세스 할건지
  • Hardware(GPU)에서 얼마나 자주 메모리를 액세스 할건지
  • OpenGL ES("GLES") 텍스처로서 메모리가 이용될 것인지
  • 비디오 인코더로서 메모리가 이용될 것인지
예를 들면, 여러분이 픽셀 포맷을 RGBA 8888로 정하고 버퍼가 소프트웨어에서 액세스 될 것(여러분의 애플리케이션이 픽셀을 직접 접근하는 것을 의미)이라고 설정했다면, 할당자는 R-G-B-A 순서로 픽셀마다 4바이트를 가진 버퍼를 생성해야 한다. 만약 버퍼가 하드웨어에서만 접근이 가능하고 GLES 텍스처로서 활용된다면, 할당자는 GLES 드라이버가 원하는 용도 (B-G-R-A 오더링과 넌리니어 "스위즐링" 레이아웃, 별도의 컬러 포맷 등)을 처리할 것이다. 하드웨어에 적합한 포맷을 사용하면 성능을 향상시킬 수 있다.

특정 플랫폼에서 일부 값을은 서로 조합시킬 수 없다. 예를 들어, "video encoder" 플래그는 YUV 픽셀을 요구하는데, 여기에 추가로 RGBA 8888 픽셀 포맷을 활용하는 "software access" 플래그을 설정하는 것은 실패할 것이다. 

 

gralloc 할당자에 의해 반환된 핸들은 바인더를 통해 프로세스 사이에서 서로 전달할 수 있다. 

 

2. SurfaceFlinger 및 WindowManager

SurfaceFlinger의 역할은 여러 소스로부터 그래픽 데이터 버퍼들을 받고, 그것들을 합성해서 display로 보내는 것이다.
HWC(Hardware Composer HAL)는 사용 가능한 하드웨어와 버퍼를 합성하는 가장 효율적인 방법을 결정하고 가상 디스플레이는 합성된 출력을 시스템 내에서 사용할 수 있도록 한다.

 

SurfaceFlinger가 버퍼를 받는 방법은 두가지가있다.

1) 앱이 포그라운드로 나올때 WindowManager에 버퍼를 요청해서 SurfaceFlinger는 레이어를 생성하며, BufferQueue의 소비자(Consumer)처럼 동작한다. 생산자(Producer) 쪽의 바인더 객체는 Window Manager를 통해 앱으로 전달되며, 이후에 앱은 바인더 객체를 통해 SurfaceFlinger 쪽으로 직접 프레임 전송을 시작할수 있다.

 

2) Android 10에 추가된 ASurfaceControl은 SurfaceFlinger가 버퍼를 받을 수 있는 또 다른 방법입니다. ASurfaceControl은 노출 영역과 SurfaceControl을 SurfaceFlinger로 전송되는 하나의 트랜잭션 패키지로 결합합니다. ASurfaceControl은 앱이 ASurfaceTransactions를 통해 업데이트하는 레이어와 연결됩니다. 그러면 앱은 래치 시간, 획득 시간 등의 정보가 포함된 ASurfaceTransactionStats를 전달하는 콜백을 통해 ASurfaceTransactions에 관한 정보를 가져옵니다.

 

WindowManager는 view 객체의 컨테이너인 window 객체를 제어한다. window 객체는 항상 surface 객체에 의해 지원된다. WindowManager는 수명 주기, 입력 및 포커스 이벤트, 화면 방향, 전환, 애니메이션, 위치, 변환, Z-order 및 창의 기타 여러 측면을 관리한다. WindowManager는 모든 창 메타데이터를 SurfaceFlinger로 전송하므로 SurfaceFlinger는 이 데이터를 사용하여 디스플레이의 노출 영역을 합성할 수 있다.

 

  • Window는 하나의 틀(Frame)을 구성하고 있습니다. 따라서 화면 요소에서의 가장 상위 요소로 볼 수 있습니다. Window는 전체 화면 구성에서 각각의 틀(Frame)안에 존재하는 영역 또는 그 전체로 볼 수 있습니다.

3. Surface 및 SurfaceHolder

Surface 클래스는 안드로이드 1.0부터 안드로이드 API에 포함되어 있었으며, 안드로이드 온라인 도움말 사이트에는 Surface 클래스에 대해서 screen compositor가 관리하는 하나의 Raw 버퍼를 가리키는 핸들이라고 간단히 설명하고 있다. 이러한 설명은 최초에 이 API 가이드가 작성된 시점에서는 정확했지만, 최근 시스템에서 Surface의 동작을 설명하기에는 부족하다.

 

Surface는 BufferQueue의 생산자 역할을 하며, SurfaceFlinger는 항상은 아니지만 흔히 이러한 BufferQueue의 소비자 역할을 한다. Surface에 렌더링을 하면, 그 결과는 버퍼 형태로 생성이 되고 이것은 소비자에게 전달된다. Surface는 여러분이 드로잉할 수 있는 단순한 메모리 영역이 아니다.

 

Display용 Surface를 위한 BufferQueue는 보통 트피플 버퍼링으로 구성된다. 그러나 버퍼들은 요구가 있으면 할당된다. 그래서 만약 생산자가 버퍼를 충분히 느리게 생성한다면(가령, 60fps 디스플레이에서 30fps 속도로 렌더링이 처리되는 정도), 큐에는 단지 두개의 할당된 버퍼만 있으면 된다. 여러분은 dumpsys SurfaceFlinger 명령의 출력 결과를 통해 모든 레이어와 연관된 버퍼들에 대한 일련의 요약 정보들을 살펴볼 수 있다.

 

대부분의 클라이언트들은 OpenGL ES 혹은 Vulkan을 사용해 Surface에 렌더링을 한다. 그러나 몇몇 클라이언트들은 Canvas를 사용해 Surface에 렌더링한다.

 

Surface와 연관된 작업을 위해서는 SurfaceHolder, 특히 SurfaceView가 필요하다. 원래 Surface는 compositor에 관리되는 버퍼를 표현하는 반면에, SurfaceHolder는 앱에 의해 관리되고, Surface의 크기나 포맷 같은 고수준의 정보를 관리한다. 이것은 자바 언어에 정의는 내부 네이티브 구현과 연결되어 있는 것과 비슷하다. 이런 식으로 이 둘을 분할해서 사용하는 것은 더 이상 유용하지 않지만,

SurfaceHolder는 오랫동안 공개 API의 부분이었다.

일반적으로 말하면, View와 관련된 어떤한 작업을 하던 간에 SurfaceHolder를 포함한다. MediaCodec 같은 다른 몇몇 API들은 Surface 상에서 동작한다. 여러분은 쉽게 SurfaceHolder를 통해 Surface를 얻을 수 있을 것이다. 그러므로 여러분이 SurfaceHolder를 가졌을 때, Surface 또한 가지게 되는 것이다. Surface의 파라미터들(가령, 크기와 포맷)을 얻거나 설정하는 API들은 SurfaceHolder를 통해 구현된다. SurfaceHolder는 SurfaceView에 포함되어있다.

 

즉,

Surface는 화면에 합성되는 픽셀을 보유한 객체로 화면을 구성하는 면으로 된 날것의 도화지 같은것으로 보면 된다.

그리고 Surface Flinger가 여러 소스로부터 그래픽 데이터 버퍼를 받고, 그것들을 합성해서 Display로 보냅니다.

 

SurfaceHolder는 Surface의 파라미터들(가령, 크기와 포맷)을 얻거나 설정하는 API들을 갖고있으며 이러한 SurfaceHolder는 SurfaceView에 포함되어있다.



  • Android Drawing의 간단한 흐름

[Lock Canvas] Surface는 날것(Pixel)의 도화지를 가지고 있습니다. 이 도화지를 가지고 누군가가 그림을 그리고 싶어 합니다. 그러면 이 날것의 도화지를 Canvas라는 그리기 쉬운 틀(Frame)에 고정(Lock)해서 그리고 싶어하는 누군가에게 주게 됩니다. 하지만 Canvas는 그 날것의 도화지가 어떻게 생겼는지 모르기때문에 Surface는 그 날것(Pixel)의 도화지를 Bitmap으로 감싸 어떻게 도화지가 어떻게 구성되어 있는지 정보를 추가해서 Canvas에게 전달합니다. 그러면 Canvas는 Bitmap의 구성정보를 활용하여 자기가 제공할 수 있는 최고의 틀(Frame)에 고정해서 그림을 그리고 싶어하는 사람에게 전달하게 됩니다.

 

[Unlock Canvas and Post] 누군가 그림을 다 그리고 난 후에 더이상 그리고 싶지 않다고 Canvas에게 알려줄 수 있습니다.
그러면 Canvas는 자기가 붙들고 있던 Surface의 Bitmap을 틀에서 때어낸 후에 Surface에게 되돌려 줍니다. Surface는 다시 받은 자기의 날것(Pixel)의 도화지를 SurfaceFliger에게 전달해서 다른 도화지들과 함께 전체화면에 부분으로서 그려지게 됩니다.

 

4. EGLSurfaces and OpenGL ES

Android는 OpenGL ES(GLES) API를 사용하여 그래픽을 렌더링한다. 하지만 OpenGL ES는 그래픽 렌더링을 위한 API를 정의하고 있지만, 윈도우 시스템을 정의하고 있지는 않다. 그래서 GLES Context를 생성하고 렌더링한 폴리곤을 스크린상에 배치하는 역할은 EGL 라이브러리를 사용한다. 

 

GLES로 뭔가를 하기 전에, GL Context생성이 먼저 필요하다. EGL에서 이것은 EGLContext와 EGLSurfce의 생성하는 것을 의미한다. GLES의 동작들은 현재 context에 적용되며, 이러한 context는 인자로 전달받는 것보다 TLS(Thread-local Storage)를 통해 접근한다. 이것은 여러분의 렌더링 코드가 어느 스레드에서 실행되는지에 대해 주의를 기울어야 하고, 스레드에서 어느 컨텍스트가 current 인지 잘 확인해야 한다는 것과 UI스레드에서 사용되지 않게함을 의미한다.


EGLSurface는 EGL에 의해 할당된 오프 스크린(off-screen)버퍼로(“pbuffer”라고 불림)만들거나, OS가 할당한 window로 만들 수 있다. EGL window surface는 eglCreateWindowSurface() 호출로 생성된다. 이 함수는 “window 객체”를 인자로 받는데, 안드로이드에서 window 객체는 SurfaceView, SurfaceTexture, SurfaceHolder 또는 Surface가 될 수 있다. 그리고 이것들 모두 내부적으로 BufferQueue를 가지고 있다. 여러분이 이 함수를 호출하면, EGL은 EGLSurface 객체를 새로 생성하고, 이것을 window 객체에 포함된 BufferQueue의 생산자 인터페이스에 연결한다. 그 순간부터, 생성된 EGLSurface에 렌더링을 하는 것은 dequeue된 버퍼에 렌더링을 하고 이 버퍼를 소비자가 사용하게끔 enqueue하는 것을 의미한다.

 

EGL은 lock/unlock 호출을 제공하지 않는다. 대신, 여러분은 드로잉 명령들을 내린 다음, eglSwapBuffers() 함수를 호출해서 현재 프레임을 전송한다. 이 함수 이름은 전면 버퍼와 후면 버퍼를 교환했던 전통 방식에서 유래됐으나, 실제 구현은 상당히 다를 수 있다.

한번에 오직 하나의 EGLSurface만이 하나의 Surface와 연결될 수 있다 — 여러분은 BuuferQueue에 연결된 한개의 생산자만 가질 수 있다 — 그러나 여러분이 EGLSurface를 소멸한다면, EGLSurface를 BufferQueue와 분리해서 이 BufferQueue가 다른 뭔가에 연결될 수 있게 한다.

 

스레드는 여러 EGLSurface들 중에 “current (EGLContext)”가 무엇을 가리킨지 변경함으로써 EGLSurface를 바꿀 수 있다. 특정 EGLSurface는 한번에 하나의 스레드에만 current가 되어야 한다.

 

EGLSurface에 대해 가장 잘못 생각하고 있는 부분이 (SurfaceHolder 처럼) Surface의 또다른 유사한 개념이라고 가정하는 것이다. Surface는 EGLSurface와 연관은 있지만 독립된 개념이다. 여러분은 Surface에 기초하지 않은 EGLSurface 상에 그림을 그릴 수 있고, EGL 없이 Surface를 사용할 수 있다. EGLSurface는 단순히 GLES에 그림을 그릴 공간을 제공해 주는 역할을 한다.

 

--- 이 이후로 부터는 앱을 구성하는 고수준 컴포넌트들에 대해 다룬다. ---

5. SurfaceView 및 GLSurfaceView

안드로이드 앱 프레임워크의 UI는 View로 시작하는 각 객체들의 계층 구조에 기반하며 모든 UI 요소는 직사각형 영역에 맞게 요소를 조정해주는 일련의 측정 및 레이아웃 프로세스를 거치게 된다. 또한 Visible View요소는 SurfaceFlinger가 생성한 Surface에 렌더링 되는데, 이 Surface는 앱이 포그라운드로 전환됐을 때 WindowManager에 의해 디스플레이 영역으로 렌더링된다. 레이아웃과 렌더링은 앱의 UI 스레드에서 수행된다.

 

  • SurfaceView

SurfaceView는 view계층구조 안에 추가적인 composite layer를 삽입 할 수 있는 Component이다. SurfaceView는 다른 View들과 동일한 종류의 인자들을 받으므로, SurfaceView의 위치와 크기를 지정하거나 그것 주위에 다른 UI 요소들을 넣을 수 있다. SurfaceView가 렌더링 할 때가 되면, 컨텐츠들은 완전히 투명하게 된다. SurfaceView의 View 부분은 마치 시스루 영역처럼 동작한다.

GL 컨텍스트 또는 미디어 디코더와 같은 외부 버퍼 소스로 렌더링할 때는 버퍼 소스의 버퍼를 복사하여 화면에 버퍼를 표시해야하며  SurfaceView를 사용하면 이를 수행할 수 있다. 

 

SurfaceView의 View 컴포넌트가 시각화 되려고 할때, 프레임워크는 WindowManager에게 “SurfaceFlinger가 새 Surface를 생성하도록” 요청한다. (이것은 동기적으로 동작하지 않는다. 때문에 여러분은 Surface 생성이 완료됐을 때 통지를 해주는 콜백을 제공해야 한다.) 디폴트로, 새로운 Surface는 ‘app UI Surface’ 뒤에 위치하지만, “Z-ordering” 기본값을 변경해서 App UI Surface 위에 Surface를 위치시킬 수 있다.

 

SurfaceView를 이용한 렌더링은 별도의 Surface에 렌더링해야 하는 경우에 유용하다. (예: Camera API 또는 OpenGL ES 컨텍스트로 렌더링하는 경우). 이 Surface에 무엇을 렌더링 하던지간에, App이 아니라 SurfaceFlinger가 직접 버퍼를 화면에 구성한다.(SurfaceView는 App과의 별도의 Window 영역으로 동작한다.) Surface는 별도의 스레드나 프로세스에 의해 렌더링 될 수 있고, app UI에 의해 수행되는 렌더링에서 격리될 수 있고, 버퍼들이 직접 SurfaceFlinger에 전달되게 한다는 점이 Surface의 진정한 장점이다.

SurfaceView로 렌더링한 후에는 UI 스레드를 사용하여 활동 수명 주기와 조율하고 필요한 경우에는 뷰 크기 또는 위치를 조정한다. 그러면 하드웨어 컴포저가 앱 UI와 다른 레이어를 혼합한다.

 

새로운 Surface는 BufferQueue의 생산자 쪽이고, 소비자쪽은 SurfaceFlinger 레이어라는 것을 다시 한번 확실히 할 필요가 있다. 여러분은 Surface를 업데이트 해서 BufferQueue에 입력할 수 있다. Surface를 활용해 Surface 기반 캔버스 기능을 사용할 수 있고, EGLSurface를 붙여서 GLES로 그림을 그리거나 MediaCodec Video Decoder의 결과를 Write하는 데 사용할 수 있다.

 

  • GLSurfaceView

GLSurfaceView 클래스는 EGL 컨텐스트를 관리하고, 스레드간 통신 및 액티비티 라이프사이클과 상호 작용을 도와주는 헬퍼 클래스들을 제공한다. 단지 그것이다. 여러분은 GLES를 사용하기 위해 반드시 GLSurfaceView를 사용할 필요는 없다.

예를 들면, GLSurfaceView는 렌더링 스레드를 생성하고, 거기에 EGL 컨텍스트를 구성한다. 액티비티가 Pause가 될때 GLSurfaceView 상태가 자동으로 정리된다. 대부분 app들은 GLSurfaceView에서 GLES를 사용하기 위해 EGL에 대해 전혀 몰라도 된다.

대부분에 경우, GLSurfaceView는 매우 도움이 되고, 이를 통해 GLES 작업을 더 쉽게 처리할 수 있다. 몇몇 경우는 다음과 같은 방식을 사용할 수 있다. GLSurfaceView가 도움이 되면 사용해라, 만약 도움이 되지 않으면 사용하지 마라.

5. SurfaceTexture

추가예정


출처 :

https://ggeutzzang.tistory.com/8?category=642827

https://source.android.com/devices/graphics/architecture?hl=en 

https://colinch4.github.io/2020-11-25/Window,-Surface,-Canvas,-View/

반응형

'Android > OpenGL ES' 카테고리의 다른 글

2) OpenGL ES로 삼각형 그리기  (0) 2021.09.28
1) OpenGL ES란?  (0) 2021.09.27