Tistory View

Android Develop/OpenGLES

Android OpenGL ES : 올바른 Framebuffer 사용법

God Dangchy What should I do? 2021. 6. 21. 04:37

ComputeShader를 작성하여 열심히 코딩을 하던 중에 어떤 기기는 잘 동작하지만 어떤 기기는 계속되는 깜빡임(flickering)으로 인해 심한 스트레스를 받고 있는 상황이었다. ComputeShader의 정확히 정해지지 않은 스펙과 Mali 드라이버의 버그 등 해결책이 보이지 않던 상황에서 한가지 "내가 간과하고 있는 것이 있는 가?" 하는 질문을 스스로에게 던지게 되었다. 웹을 뒤지고 뒤져서 글을 하나 발견하게 되었는 데, "FrameBuffer를 올바르게 사용하는 법"이라는 글이었다.

 

https://community.arm.com/developer/tools-software/graphics/b/blog/posts/mali-performance-2-how-to-correctly-handle-framebuffers

 

Mali Performance: How to correctly handle framebuffers

This week I look at how, and more importantly when, the Mali driver stack turns OpenGL ES API activity into the hardware workloads needed for rendering.

community.arm.com

 

이 글은 Mali 드라이버를 만드는 사람이 작성한 것 같다. 어떻게 FrameBuffer가 동작하는지 설명이 되었다. Mali기준으로 작성되었지만, 아마 다른 브랜드의 GPU도 그리 차이가 있을 것 같지는 않다.

 

이렇게 남겨두지 않으면, 언젠가 또 까먹고, 스트레스의 바다에 빠져버릴 것 같아 남겨 두기로 한다.

 

위의 글이 워낙 잘 정리가 되어 있어서, 영어를 잘하는 사람은 그냥 가서 보는 편이 훨씬 유리할 것이다. 단지 기술적인 내용이라 하드코어한 부분도 있지만, 필자[갓댕치:티스토리블로그]의 오역으로 인해 잘 못된 정보가 전달 될 수 있는 부분을 감안한다면, 원문을 참고 하기를 권장한다. 이 글은 번역에 치중하지 않고, 이해하기 쉽게(내가) 내용을 작성하였다.

 

 

FrameBuffer는 어떻게 동작하는 가?

1. FrameBuffer는 두가지가 있고, 이 둘의 행동방식은 다르다.

  기본FrameBuffer와 사용자가 일부러 만들어낸 FrameBuffer로 나뉜다. 좀 더 간단히 말하면 "0번"인 기본 FrameBuffer와 아닌 것으로 나뉜다.(0번인 기본Framebuffer제외한 나머지를 이후에 사용자Framebuffer라 칭하겠다.)

 

0번인 기본 FrameBuffer는 eglSwapBuffer를 하면 color, depth, stencil의 상태가 "Undefined"로 바뀐다. eglSwapBuffer는 렌더링 작업을 flush하게되고, 이 때, color, depth, stencil의 사용이 완료 되었으니 Undefined로 바뀌는 것이다.

  

사용자Framebuffer들은 eglSwapBuffer 같은 것이 없다, 이 FrameBuffer가 "Unbound"되면 color, depth, stencil버퍼가 GPU의 메모리로 이동, 저장된다.

 

0번인지 아닌지가 중요한 구분점이 된다. 0번이라도 renderbuffer를 이용하여 offscreen buffer를 만들어 낼 수 있다.
이 때, 이 offscreen buffer도 기본FrameBuffer 방식으로 처리된다.

  

다시 다른 말로,

기본 FrameBuffer는 Unbound되면( glBindFramebuffer로 사용자Framebuffer로 바뀌는 경우) 그냥 데이터를 그대로 가지고 있을 뿐이다.

 

사용자Framebuffer는 Unbound되면( glBindFramebuffer 다른 사용자Framebuffer로 바뀌는 경우나 기본[0번]FrameBuffer로 바뀌는 경우) 해당 Framebuffer의 내용이 이동[저장]된다는 것이다.

 

이 말을 또 다시 다른 말로,

이동[저장]이 된다는 것이 그 만큼 시간이 소요되어 속도 저하를 가지고 오게 된다는 것이다. 이 상황은 또 다른 속도 저하를 발생 시키는 데, 사용자 FrameBuffer를 bind한 후 여기에 또 다른 렌더링 작업을 할 경우, 기존에 저장된 데이터를 다시 불러온다는 것이다. 당연히 기존의 정보가 있어야 그 위에 그릴 수 있기 때문에 다시 읽어오는 과정이 필요하게 된다.

 

기본 Framebuffer는 eglSwapBuffer로 인해 Undefined상태가 되게 되고, 사용자 Framebuffer는 다른 방법을 사용하여 Undefined상태가 되게 된다(방법은 다음에 나온다). Undefined상태가 아니면 bind후 다시 사용될 때 다시 읽어오는 과정이 발생하게 된다.

(갓댕치주 : 워낙 사용자Framebuffer를 bind/unbind하게 되니, 기본 Framebuffer는 메모리복사같은 작업은 없는 것 같다. 틀릴 수 있다;;)

 

사용자 Framebuffer는 이런 식으로 동작하기 때문에, "화면상에 한 frame[Frame N]"을 Rendering할 때마다, 한번씩만 사용자 Framebuffer에 Rendering를 하고, "같은 화면상에 frame[Frame N]" 내에서는 다시 bind하여 사용하지 않는 것이 속도를 떨어뜨리지 않는 방법이다.

 

glClear는 언제 쓰는 것인가?

일단 glClear는 드라이버에게 "이전상태는 필요없음"을 전달하는 것이다. 따라서 rendering작업을 위해 메모리에서 다시 불러오는 작업은 하지 않도록 한다. 또한 undefined된 것을 "clear 상태"로 바꾸는 것이다.

이 때, 완전히 불러오지 않기위해(일부분을 불러올 수도 있다.), color, depth, stencil을 전부 clear해줘야 하고 또한 Scissor또한 없어야 한다. 일부라도 clear되지 않는다면 다시 읽어오는 작업이 발생할 것이다.

 

glInvalidateFramebuffer는 언제 쓰는 것인가?

glInvalidateFramebuffer는 이 사용자 버퍼가 unbind(flush가 일어난다)될 때, 어떤 것을 저장하지 않을 지 지정하는 것이다. 지정하지 않으면 일단 전부 이동[저장]이 일어나기 때문에 필요없는 것을 "Undefined(Invalidate)상태"로 바꿔버리는 것이다. 보통 rendering작업의 결과는 color buffer로 최종 결과물이 나오기 때문에, depth나 stencil의 경우는 저장할 필요가 없다. 이 때 사용하면 depth나 stencil을 복사하는 과정이 없어지는 것이다.

glInvalidateFramebuffer의 호출 시점이 중요한다. [Frame N]에서 사용이 끝난 것은 [Frame N]에서 바로 glInvalidateFramebuffer를 사용해야한다. [Frame N+1]에서 사용하는 실수가 있는 데, 이는 이미 저장이 다 된 상태라 의미가 없다.

glInvalidateFramebuffer는 3.0이상에서만 사용할 수 있으니, 2.0에서는 glDiscardFramebufferExt()을 사용해야 한다.물론 지원하는지 먼저 체크하고...(GL_EXT_discard_framebuffer)

 

 

잘못된 예(속도가 줄어드는 예)

위의 링크된 글에서 잘못된 사용법과 일어나는 상황을 하나하나 풀어서 설명하도록 하겠다.(틀릴 수 있음)

 

#define ALL_BUFFERS COLOR_BUFFER_BIT | DEPTH_BUFFER_BIT | STENCIL_BUFFER_BIT

glClear( ALL_BUFFERS )    //기본framebuffer의 상태를 clear[초기화]한다.[현재는 기본framebuffer다]

                                  // Rendering작업이 일어나도 기존[이전 frame]의 내용을 가지고 오지 않는다.

glDraw...( ... )                 // 기본 Framebuffer에 뭔가를 그린다.

glBindFramebuffer( 1 )    // 사용자 Framebuffer[1]로 바꾼다. 기본 Framebuffer는 단순히 Unbind될 뿐이다.(복사없다.)

glClear( ALL_BUFFERS )    // 사용자 Framebuffer[1]을 clear한다. 기존에 있던 정보는 필요가 없다고 표시한다.

glDraw...( ... )                  // 사용자 Framebuffer[1]에 뭔가를 그린다.

                                   // clear상태에서 그리므로 이전 정보를 복사해오지 않는다.

glBindFramebuffer(0)        // 기본 버퍼로 바꾼다. 사용자FBO는 unbind되고 또 flush된다.

                                   //이 때 [1]의 데이터(color/depth/stencil)는 이동[저장]이 된다.

 

glDraw...( ... )                 // 기본 Framebuffer에 뭔가를 또 그린다.

glBindFramebuffer( 1 )    // 기본 Framebuffer에서 사용자 framebuffer[1]를 다시 bind한다.

                               // 이제 그리기 작업 같은 것을 한다면 다시 메모리에서 불러와야 한다. 위에다 그려야 되니

glDraw...( ... )              // 그리기 작업을 한다. 따라서 그리기 작업 이전에 메모리에서 다시 불러오는 상황이 발생한다

glBindFramebuffer(0)    // 기본버퍼로 바꾼다. 사용자[1]은 또 다시 저장된다.

glDraw...( ... )              // 기본 Framebuffer에 또 뭔가를 그린다.

eglSwapBuffers()          // 실제 기본 Framebuffer에 모든 rendering이 일어나게 되고,

                               // color/depth/stencil를 undefined 상태가 될 것이다.

 

 

 

끝..

 

 

필자[갓댕치]가 이 내용이 필요했던 이유는, framebuffer에 color buffer를 texture로 지정해 놓았다. vertex/fragment shader를 이용하여 뭔가를 그린 다음, computeShader를 이용하여 후가공을 하는 과정이 이었는 데, 전체 full-screen을 이용하기 때문에 glClear를 해주지 않았다.(그래도 되긴 해야 할 듯한 데..)

그로인해 드라이버가 꼬였는지, glClear를 해준 후 flickering이 사라졌다.

또한 Mali에는 버그가 있는 데, 그 문제를 해결하기위해 glClear후 unbind하고 다시 bind하여 쓰는 이상한 방식으로 일단 Mali버그는 해결 했다.

(이게 어떤 것 들이 버그가 있는 지 드라이버 버전마다 체크할 수가 없어서.. 22[Mali driver버전, Android버전아님]이상은 고쳐 졌다고 하는데.. 필자가 22이상의 기계가 없어서 테스트를 못한다는 이상한 결론이..)

 

 

 

 

Replies
Reply Write