Tistory View

Android Develop/OpenGLES

Android OpenGL ES 버전 호환성 맞추기

God Dangchy What should I do? 2021. 6. 26. 16:13

안드로이드의 최소버전(minSdkVersion)을 기준으로 네이티브 코드는 링크가 된다.[*1], 그러다 보니 이 minSdkVersion을 강제로 올려야 되는 상황이 발생한다. 만들고 있는 앱을 지원하지 않는 기능을 제외하고는 낮은 버전에서도 돌리고 싶은 데, 이 minSdkVersion값을 올려버리면 아예 지원하는 기기가 달라지기에 문제가 아닐 수 없다.

어쩔 수 없이 다른 방법을 사용해야 하는 데, 기기에 깔린 libGLESv3.so 파일을 읽어드려서 필요한 함수포인터를 알아 낼 수가 있다.

 

dlOpen을 이용하기

dlOpen함수를 사용하여 dlsym을 통하여 함수의 포인터를 구할 수도 있지만, 이 또한 문제가 발생할 수 있는 데, 기기의 제조사가 OpenGL ES 3.x용 libGLESv3.so파일을 만드는 과정에서 제조사에 따른 또다른 파일도 존재할 수도 있다.

문제가 없을 수도 있다. libGLESv3.so파일을 dlopen하면 관련 so파일이 따라 올 수 도 있기에.. 테스트를 안해봐서.. 할 계획도 없지만..

 

 

eglGetProcAddress

EGL에서는 이 것을 대신 해주는 eglGetProcAddress함수가 이미 존재한다. 일일이 dlOpen을 할 필요가 없이 바로 이 eglGetProcAddress함수를 통해서 함수의 포인터를 받아 버릴 수 있다. 이 것은 2.0에서도 동일하게 적용되기 때문에, 3.x와 2.0의 GLESv*파일을 따로 따로 dlOpen해서 확인할 필요도 없다. 이 eglGetProcAddress는 확장(Extension) 함수를 불러올 때도 사용할 수 있다.

 

주의점이 존재하는 데, eglGetProcAdress함수를 통하여 함수의 포인터가 null이 아닌 값이 돌려준다고 해도 이 함수를 제대로 지원한다는 뜻은 아니다. GL_VERSION이나 GL_EXTENSION이나 EGL_EXTENSIONS에 있는지 확인해야만 한다. 실제로 확인하지 않고, 함수를 호출해보면, "지원하지 않는다"는 식의 Log를 출력하기도 한다(그나마 안 죽으면 다행이다)

 

 

예제

glMapBufferRange함수는 3.0이상에서 지원한다. 2.0에서는 존재하지 않는다.[*2],

이 함수는 3.0이상에서 지원하기 때문에 일단 현재의 OpenGL ES 버전이 3.0이상인지를 확인 해야 한다.

확인 후 3.0이상이면 이 glMapBufferRange함수를 사용하고, 이하인 경우 다른 방식으로 처리하는 코드로 작성하면 된다.

 

// 필요한 함수의 형태를 정의
typedef void* (*func_glMapBufferRange)(GLenum, GLintptr, GLsizeiptr, GLbitfield);

// 함수의 포인터를 저장
static func_glMapBufferRange f_glMapBufferRange = nullptr;


// GL ES 버전을 가지고 오는 함수..
static bool GetGLVersion( GLint* major, GLint* minor ) {
    bool rc = false;
    const char *version = (const char*)glGetString( GL_VERSION );
    char szOpenGL[128];
    char szES[128];
    int  majorV, minorV;

    assert( version );
    // sscanf쓰면 안되지만 귀찮아서 그냥 쓰겠다. 
    // 대소문자 문제가 있을 수 있는 데, 그 정도는 알아서..
    if( 4 == sscanf( version, "%s %s %d.%d", szOpenGL, szES, &majorV, &minorV ) ) {
        if( 0 == strcmp( szOpenGL, "OpenGL" ) && 0 == strcmp( szES, "ES" ) ) {
            *major = majorV;
            *minor = minorV;
            rc = true;
        }
    }
    return rc;
}




void 초기화() {
    GLint glMajorVer, glMinorVer;
    if( !GetGLVersion( &glMajorVer, &glMinorVer ) ) {
        LOGE( TAG, "GLGetVersion Failure" );
        return false;
    }
    // 3.0 이상인지를 확인 후 함수의 포인터를 땡겨 온다.
    if( glMajorVer >= 3 ) {
        f_glMapBufferRange = (func_glMapBufferRange) eglGetProcAddress("glMapBufferRange");
        LOGD( TAG, "f_glMapBufferRange = %p", f_glMapBufferRange );
    }
}

void 사용하는_함수() {
	
    if( f_glMapBufferRange ) {
        f_glMapBufferRange( GL_ARRAY_BUFFER, 0, 1024, GL_MAP_READ_BIT );
    } else {
        // extension을 사용하거나 다른 방식(fbo로 바인딩후 glReadPixels..)
        LOGD( TAG, "glMapBufferRange Not support" );
    }
}

위와 같은 방식으로 필요한 함수를 다 가져와서 사용하면 된다.

여기서 주의할 사항이 있는 데, 버전을 체크하는 코드를 정확히 잘 만들어야 한다.

 

예를 들어, 3.11[*3]버전에서 지원하고 3.2[*3]버전은 지원하지 않는다면, 이 상황에서 만약에 float으로 가져와 버전을 비교한다면 높은 버전과 낮은 버전이 뒤집할 수 있다. (3.11이 3.2보다 버전이 높은 것이다!)

또다른 예는 위의 코드처럼 int로 major와 minor를 구분해서 가지고 온 상황에서, 3.11이상에서 지원한다는 코드를 작성하려면 다음과 같이 작성해야 한다.

// 4이상이거나 3.11이상인 조건
if( major > 3 || (major == 3 && minor >= 11 ) ) {
    // 그래 지원 한다. 함수 포인터를 땡겨 온다.
}

 

 

define 처리하기

include하지 않고, #define 값들을 사용하려면 다음과 같이 필요한 곳이 값들을 찾아서 일일이 넣어주어야 한다.

#ifndef GL_MAP_READ_BIT
#define GL_MAP_READ_BIT   0x0001
#endif

특히 2.0[GLESv2]과 링크하고 <GLES3/gl3.h>하지 않을 경우 좀 유용하다. 필자는 귀찮아서 그냥 <GLES3/gl3.h>를 inlcude해서 쓰기는 한다.


*1 : externalNativeBuild를 이용할 때는 minSdkVersion을 기준으로 된다.

      -DANDROID_PLATFORM를 cc에 지정해 버리는 방법이 있기는 하다.

 

*2 : GL_OES_mapbuffer나 EXT_map_buffer_range확장을 지원한다면 거의 비슷하게 이용할 수 있다.

*3 : 3.11, 4.0버전은 현재 없다. 예를 사용된 것 뿐이다.

Replies
Reply Write