Tistory View

Android Develop/GLES2.0

기본 사용법 : GLSurfaceView

God Dangchy What should I do? 2020. 7. 19. 21:01

GLSurfaceView

안드로이드는 OpenGLES을 사용할 수 있는 방법 중에 간단하면서도 쓸만한 GLSurfaceView를 지원해 주고 있다.

속도가 빠른 장점이 있다. 정확히 TextureView를 이용한 것 보다는 빠른 것 같다. 


GLSurfaceView는 화면을 그리는 코드가 Render Thread로 분리되어 실행되기 때문에 MainThread에 주는 무리를 줄일 수 있고, 분리된 Thread로 인해 빠르게 화면을 그릴 수가 있다.





기본 사용법

1. AndroidManifest.xml설정

2. 초기화 및 Renderer지정

3. onPause/onResume




1. AndroidManifest.xml 설정


일단 OpenGLES를 사용한다는 것을 알려야 하기 때문에 AndroidManifest.xml파일에 다음 줄을 추가해줘야 한다.

<uses-feature android:glEsVersion="0x00020000" android:required="true" />

OpenGLES 2.0을 사용할 것이라는 것을 알리지만 기기가 3.0이나 3.1을 사용할수 있는 경우에도 위와같이 놓고 3.0이나 3.1을 지원하는 지는 프로그램코드로 체크하여 3.0이나 3.1을 사용할 수 있으니, 그냥 2.0으로 설정해도 된다.




2. 초기화 및 Renderer지정

일단 GLSurfaceView를 상속받아 Class를 하나 만든다. 직접 사용해도 되지만, 필자는 이 방식을 주로 쓴다.


public class MySurfaceView extends GLSurfaceView
{
public CPageSurfaceView(Context context) {
super(context);
this._init( context, null );
}

public CPageSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
this._init( context, attrs );
}

private void _init( Context context, AttributeSet attrs ) {

this.setEGLContextClientVersion( 2 );

   int redSize     =  8;
int greenSize = 8;
int blueSize = 8;
int alphaSize = 8;
int depthSize = 16;
int stencilSize = 8;

this.setEGLConfigChooser( redSize,greenSize, blueSize,alphaSize, depthSize, stencilSize );
this.setRenderer( ... );


this.setRenderMode( RENDERMODE_WHEN_DIRTY );

this.requestRender();
}
}



상속을 받았으니 기본적으로 제공해줘야 하는 생성자를 만들고 생성자마다 공통적으로 실행되는 부분을 _init(...)함수에 묶어 놓아서 생성자마다 _init(...)함수를 호출하도록 만들었다.



_init(..)함수를 살펴보면

this.EGLContextClientVersion(2); 

OpenGLES 2.0를 사용한다.


this.setEGLConfigChooser(,...);

사용할 화면색상과 Depth를 설정한다. 안드로이드는 기기마다 버전마다 이 값들이 다르게 되어있다. 예전의 Galaxy S2의 경우 565방식이 기본으로 되어있어서(확인안함) 이 것을 강제로 어느 기기에서나 동일하게 맞춰주기위해 이 코드를 넣었다.


this.setRenderer(...);

실제 OpenGLES를 이용하여 그리는 코드(OpenGLES명령)들은 GLContext가 존재하는 Thread에서만 실행 시킬 수가 있다. 이 Render Thread에서 실행할 코드를 적을 수 있는 함수들을 미리 Interface로 만들어 두었기 때문에 우리는 이 interface내의 함수에 필요한 코드를 추가하기만 하면 된다. GLSurfaceView는 자동으로 Render Thread를 생성하여 필요할 때마다 이 interface내의 함수를 호출해 주게 된다.

이 interface에는 onSurfaceCreated, onSurfaceChanged, onDrawFrame 세 개의 함수가 있다. 이 interface의 설명은 좀 있다 하겠다.



this.setRenderMode( ... );

       [RENDERMODE_WHEN_DIRTY]와 [RENDERMODE_CONTINUOUSLY] 두가지 중에 선택할 수가 있다.


       RENDERMODE_CONTINUOUSLY

지속적으로 Renderer의 OnDrawFrame()함수를 호출한다. OnDrawFrame호출은 기기가 지원하는 최대 fps로 호출이 되기 때문에 60fps를 지원하는 기기에서 60fps이상으로 호출이 되는 일은 없다. 화면이 갱싱되는 상황에서 만 호출되기 때문에 Cpu를 100%사용하는 일은 RENDERMODE_CONTINUOUSLY라도 발생하지 않게 된다.


       RENDERMODE_WHEN_DIRTY

화면을 그려야 할 때만 호출이 된다. 화면의 layout이 변경된다든지, Activity가 onResume된다든지 하는 상황에서 호출이 자동으로 되지만, 화면에 그려질 내용을 변경한 다음에는 강제로 화면을 그리게하는 함수[requestRender()]를 호출할 때만 OnDrawFrame을 호출해서 화면을 갱신하게 된다. 지속적으로 그리면 화면내용에 변화가 없어도 계속 CPU와 GPU가 돌면서 밧데리가 빨리 닳게 될테니, 게임같은 상황이 아니면 이 방식을 주로 사용하게 될 것이다. requestRender함수를 아무리 빠른 속도로 호출한다고 해도 기기의 최대 fps로만 OnDrawFrame을 호출하므로 requestRender함수는 화면을 새로 그려야하는 상황마다 그냥 호출해버리면 된다.




GLSurfaceView.Renderer interface 함수들

void onSurfaceCreated( GL10 gl, EGLConfig config );

void onSurfaceChanged( GL10 gl, int width, int height );     

void onDrawFrame(GL10 gl );



이 세 개의 함수는 Render Thread에서 호출되게 된다. 이미 GLContext를 보유하고 있기 때문에 OpenGLES함수를 맘대로 사용할 수가 있다. 다른 말로 다른 곳에서는 OpenGLES함수를 사용할 수가 없다. Thread가 분리되어 실행되기 때문에 이 곳에서 많은 작업을 해도 MainThread(UIThread)는 영향을 거의 끼치지 않게 된다.



onSurfaceCreated

Surface가 만들어지고 실행이 된다. 실제 GLContext가 새로 만들어 졌다고 보면 무방하다. 이제부터 GL명령을 사용할 수 있다. 이 함수에는 주로 전반적으로 사용될 gl-program을 생성한다던지, vbo나 texture를 만드는 코드를 주로 넣게 된다. 


onSurfaceChanged

주로 화면(VIew)의 크기가 변경되면 호출이 된다. 크기가 변경되니 주로 할 일은 prespectiv-matrix를 다시 설정해주는 코드가 들어 가게 된다.


onDrawFrame

화면이 그려져야 할 경우 호출되게 된다. 실제 대부분의 코드가 이 곳에 위치하게 된다. onDrawFrame함수호출이 끝나면 swapBuffer함수가 자동으로 호출되기 때문에 꼭 화면의 내용을 그려줘야 한다. 그릴 것이 없다고 그리지 않고 끝내버리면 엉뚱한 화면을 그리게 될 것이다.

당연히 GLContext를 가 존재하니 필요에 따라서는 이 곳에서도 program,vbo,texture등의 작업을 할 수 있다. onSurfaceCreated는 전반적인 vbo같은 녀석을 주로 다루게 되지만, 실제 OpenGLES관련 코드는 이 곳에서 전부 처리해야 될 것이다.


다시 onSurfaceCreated

사실상 GLContext가 새로 만들어진다음에 이 함수가 호출되게 된다. 이 말은 이미 만들어놓은 vbo나 texture같은 것들은 이전의 GLContext에 귀속이 되어있던거라 사실상 이미 소멸되고 없다. 따라서 다시 다 만들어 줘야 한다.


실제 기기를 회전시키면 onSurfaceCreated함수가 다시 호출이 된다. 세로화면에서 가로화면으로 변경되었다면, 세로화면에서 사용하던 vbo,texture등은 다 사라지고 없다는 뜻이 된다. 그래서 다시 다 만들어 줘야 한다.

setPreserveEGLContenxtOnPause() 함수를 통하여 소멸이 되지 않게 할 수 있는 데, 필자는 그냥 다시 만드는 것을 주로 쓴다.(이게 더 편하다.)



위의 세 함수는 전부 (GL10 gl)의 parameter가 있지만, OpenGLES 1.0에서는 사용하지만(맞나?), 2.0이상일 경우 사용할 필요가 없다. 




3. onPause / onResumed

GLSurfaceView에는 onPause와 onResume함수가 존재한다. GLSurfaceView는 Render Thread를 분리해서 동작하는 방식이기에 Activity가 pause되면 화면에 더이상 그릴 수도 없고, 게다가 Thread가 계속 돌고 있으니 밧데리를 소비하고 있게 된다. 또한 GPU가 GLContext를 무한정 제공하는 것도 아니기 때문에 다른 App에서도 OpenGLES를 사용한다면 GLContext를 반납해줘야 한다. 또한 이 분리된 RenderThread도 멈추거나 끝내야하기 따문에 필요한다. 그러다 다시 사용자가 우리의 아름다운 App으로 돌아오면 다시 모든 것을 작업을 해줘야 한다.(onResume)


이 작업을 바로 이 함수들이 해준다. Activity의 onPause와 onResume에서 이 함수를 호출해서 이 작업을 처리할 수 있게 꼭 넣어 줘야한다.(Activity에 넣는 것이다.)







여기까지가 기본적으로 GLSurfaceView를 사용하려면 해줘야 하는 작업들이다.

여기까지 적용된 코드는 다음과 같다.


public class CPageSurfaceView extends GLSurfaceView
implements
android.opengl.GLSurfaceView.Renderer // 렌더러를 이 class에 포함 시킨다.
{
private static final String TAG = "CPageSurfaceView";

public CPageSurfaceView(Context context) {
super(context);
this._init( context, null );
}

public CPageSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
this._init( context, attrs );
}

private void _init( Context context, AttributeSet attrs ) {

this.setEGLContextClientVersion( 2 );

int redSize = 8;
int greenSize = 8;
int blueSize = 8;
int alphaSize = 8;
int depthSize = 16;
int stencilSize = 8;

this.setEGLConfigChooser( redSize,greenSize, blueSize,alphaSize, depthSize, stencilSize );


// 렌더러를 바로 this instance 로 지정한다. 이게 상속받아 쓰면 좋은 점이다.
this.setRenderer( this );

this.setRenderMode( RENDERMODE_WHEN_DIRTY );

this.requestRender(); // 안해 줘도 될 것 같은 데, 호출한다면 더 명확해진다.
}

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {

        // 전반적으로 사용되며 변하지 않는 GL Object를 만든다.

// 화면 지우는 색깔, 한번만 하면 되는 작업중에 하나다. // 지우는 것이 아니고 색깔을 지정하는 것이다.

GLES20.glClearColor( 0.0F, 0.0F, 0.0F, 1.0F );
this.requestRender();
}

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
        // 주로 perspectve-matrix작업을 한다.
}

@Override
public void onDrawFrame(GL10 gl) {

        GLES20.glClear();

}

}




Activity에 다음의 코드를 꼭 넣어줘야 한다.

@Override
protected void onPause() {
super.onPause();
mSurfaceView.onPause();
}

@Override
protected void onResume() {
super.onResume();
mSurfaceView.onResume();
}




[GL]SurfaceView는 다른 View와 어울리도록 설계된 것이 아니다. 그냥 자기 혼자 노는 방식으로 설계가 되었기때문에, SufaceView를 이동한다든지 하는 부분은 애초에 고려되지 않는 부분이다. [GL]SurfaceView를 이리 저리 옮기게 되면 뒤늦게 따라오게 되고 거의 혼자 놀게된다.(원래 그렇게 만들어진 것이다.) 이렇게 Layout에 맞춰 움직여야 하는 상황이라면 [GL]SurfaceView를 사용하지 말고 TextureView를 이용해야한다.







TextureView와의 차이점

 TextureView는 다시 그려질때 마다 childView와 TextureView의 결과를 섞어서 출력을 해줘야해서 속도가 떨어지는 경우가 있다. 하지만 SurfaceView는 그냥 화면에 구멍을 내서 그려버리는 방식이라 다른 View와의 관련성을 따지지 않아 속도면에서 이 점이 있다. 실제 빠른 속도가 필요한 곳(게임같은 곳)에는 TextureView보다는 SurfaceView를 사용해야 프레임저하를 막을 수 있다.(요즘은 기기들이 성능이 좋아서 별의미가 없어 보이는 내용이기도 하다.)

DRM이 걸린 Content를 플레이해야하는 곳에서는 SurfaceView만을 써야 되는 것으로 알고 있지만.. 이 건 TextureView가 언젠가 지원될지도 모르는 문제이긴 하다.


'Android Develop > GLES2.0' 카테고리의 다른 글

기본 사용법 : GLSurfaceView  (2) 2020.07.19
YUV shader  (0) 2017.10.30
Replies
Reply Write