Tistory View

Android Develop/OpenGLES

Android OpenGLES FenceSync 이해하기

God Dangchy What should I do? 2021. 3. 16. 21:11

Single Thread에서 작업을 할 경우, 이 FenceSync라는 녀석은 필요가 없다. 하지만 이전에 포스트했던 shared Context를 사용할 경우 문제가 발생할 수 있다. 다음의 시나리오를 통해 발생할 수 있는 상황을 연출해보자.

 

이 전에 OpenGLES를 멀티 쓰레드로 사용하는 방법은 다음의 링크를 이용하기 바란다.

jamssoft.tistory.com/227

 

안드로이드 NDK OpenGLES 초기화 및 Shared Context

OpenGLES 초기화는 자주하는 작업은 아니지만, 다시 하려고 하면 어떻게 하는지 까먹는 작업이라 이렇게 정리를 해둔다. NDK로 코드가 작성되었으며, 이게 우끼게도 Java는 다른 식으로 작성해야 문

jamssoft.tistory.com

 

 

 

Thread B에서 texture에 GPU로 Texture데이터를 보낸다. Thread A에서는 이 텍스쳐를 화면에 그려야하는 상황이다.

 

Thread A에서 Texture를 그리는 시점에는 아직 Image Data를 보내지도 않은 상황이다.

 

Thread B에서 genTextures로 빈 texture는 만들어지고, 이 변수를 Thread A에서는 사용할 수가 있다. 하지만 화면에 그리는 시점에는 glTexImage2D는 실행이 되지도 않았으며, 실행이 된 다음이라고 해도, 이미지 데이터는 GPU에 도착하지 않은 상황일 수 있다. 이미지 데이터가 없으니 또는 데이터의 일부만 도착한 경우 이를 화면에 그리면 깨진 Texture를 화면에 그려버리는 상황이 발생하게 된다.

 

따라서, 이미지 데이터가 GPU에서 처리가 되었는 지 알아낼 방법이 필요하게 된다. 이 때 사용하는 것이 FenceSync라는 녀석이다. 예제는 Texture를 다루었지만, 다른 GL-Object들 특히 크기가 큰 데이터(VBO같은 것)를 전송할 때는 모두 사용해야 한다.

 

이 글을 보는 사람들이 대부분 개발자들일 것이다. 미리 사전지식이 머리속에 있기에 헤깔릴 수 있는 부분이, 이 FenceSync에 Sync라는 단어로 인해 Mutex와 같은 동작이라고 생각할 수 있다. 하지만 이 것은 Mutex의 동작이 아니며, 전혀 다른 방식이니, 머리속에서 Mutual-Exclusive라는 단어를 지우고 글을 읽기 바란다.

 

동작방식

아래의 설명은 이해를 돕기위한 설명으로 정확히 이 방식으로 동작하는지는 필자도 확인해보지는 못했다. 그냥 필자의 뇌피셜이다. 하지만, 이해하는 데에는 큰 어려움이 없을 것이다.

 

 

FenceSync 처리 전

 

Thread B는 glFenceSync() 함수로 FenceSync Object를 만든다. 또한 SyncFence(GL명령)을 GL command Queue에 담는다.

Thread A는 glClientWaitSync()함수로 FenceSync Object가 Signal될 때까지 기다리게 된다.

 

GPU가 GL command Queue에 명령을 순서대로 처리하면, SyncFence 명령이 GPU에 도착하게 된다.

여기서 glBindTexture와 glTexImage2D명령은 이미 처리된 상태이다.

 

FenceSync 처리 후

 

GPU는 도착한 FenceSync명령에 의해 FenceSync Object를 SIGNALED상태로 변경한다.

Thread A의 glClientWaitSync함수가 기다림을 멈추게 되고 실행을 계속하게 된다.

 

 

FenceSync Object가 SIGNAL상태가 되었다는 것은 이전의 명령들인 glTexImage2D명령이 처리되었다는 뜻이 되기 때문에 Thread A는 Texture를 안심하고 그릴 수 있게 된다.

 

 

 

FenceSync 관련함수

GLSync glFenceSync( GLenum condition, GLbitfield flags );

FenceSync를 만든다.

현재 condition은 GL_SYNC_GPU_COMMANDS_COMPLETE값만 지원하고, flags도 "0"을 넘겨야만 동작하기 때문에 다음과 같이 쓸 수 밖에 없다.

GLSync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);

 

 

GLenum glClientWaitSync( GLsync sync, GLbitfield flags, GLuint64 timeout);

FenceSync Object가 Signal될 때까지 기다리는 함수다.

sync는 glFenceSync로 만들어진 변수고, flags에는 GL_SYNC_FLUSH_COMMANDS_BIT를 쓸 수 있는 데, 이 것은 이 기다리기 전에 glFlush()를 호출해 주는 역할을 한다. timeout은 나노초단위로 기다리는 시간을 지정한다. 0을 넣으면 즉시 리턴한다.

 

리턴값은

   GL_ALREADY_SIGNALED     : 이미 시그널됨

   GL_TIMEOUT_EXPIRED       : 타임아웃됨(시그널 되지 않음)

   GL_CONDITION_SATISFIED : 시그널 됨(타임아웃 되기 전에)

   GL_WAIT_FAILED              : 뭔가 문제 있음.

 

void glDeleteSync( GLsync sync )

fenceSync 객체를 삭제한다.

 

 

void glWaitSync( GLsync sync, GLbitfield flags, GLuint64 timeout );

이 함수는 쓸일이 없을 것 같아 필자는 그냥 생략하겠다. 주의 할 것은 이게 사용법이 딱 정해져 있고, glClientWaitSync와 사용법이 좀 다르니, 필요한 사람은 다음의 링크를 통해 확인하고 사용하기 바란다.

docs.gl/es3/glWaitSync

 

 

 

 

Signal되었는 지 확인하기

 glClientSync를 사용하는 방법이 있고, glGetSynciv함수를 이용하는 방법이 있다.

glClientSync에 타임아웃값을 0으로 넣고, 리턴값으로 확인 할 수 있고, glGetSynciv함수로 SIGNAL상태를 알아내는 두가지 방법중에 어떤 것이 빠르냐라고 질문을 한다면... 기기마다 다른다.. 좀 더 상세히 말하면, 어떤 회사의 GPU를 쓰느냐어떤 운영체제를 쓰느냐에 따라 다르다, 실제 테스트 해봐서 빠른 쪽을 써야한다. 필자의 경우 Android에서는 glGetSynciv함수를 통해서 쓰는 쪽이 좀 더 빠른 것 같다.

 

 

 

glFlush가 중요하다.

FenceSync를 만든 쓰레드에서 꼭 glFlush를 호출해줘야 한다. glClientWaitSync함수에 GL_SYNC_FLUSH_COMMANDS_BIT를 전달할 수도 있지만, 전달 해도 필자는 glFlush를 호출해 주기를 권장한다.

 

Thread B에서 만든 FenceSync가 실제 GPU에 도달을 해야 하는데, 명령어 큐가 가득 차지 않으면, 이게 그냥 큐에 있기만하는 수가 있다. 실제로 이런 경우가 더 많다. 그러면 Thread A는 영원히 기다리고 되는 상황이 발생한다.

 

 

 

Reference

www.khronos.org/opengl/wiki/Sync_Object

arm-software.github.io/opengl-es-sdk-for-android/thread_sync.html <- 여기가 쓸 만함

'Android Develop > OpenGLES' 카테고리의 다른 글

Android OpenGLES FenceSync 이해하기  (0) 2021.03.16
안드로이드 NDK OpenGLES 초기화 및 Shared Context  (0) 2021.03.15
기본 사용법 : GLSurfaceView  (2) 2020.07.19
YUV shader  (0) 2017.10.30
Replies
Reply Write