Tistory View

Android Develop/OpenGLES

Android GPU로 계산하기 : ComputeShader #1 소개

God Dangchy What should I do? 2021. 6. 26. 15:05

GPU도 연산회로를 포함하고 있다. 게다가 이 연산은 3차원 공간과 색공간을 처리할 수 있는 특화된 연산함수를 포함한다. 주로 삼각함수와 고등학교에서 배운 벡터연산 등을 빠르게 계산할 수 있게 설계가 되어있다. 이 것을 잘 활용한다면 CPU의 부하를 줄이고 코어수가 훨씬 많은 GPU를 이용하기 때문에 더 많은 계산을 할 수 있다.

 

OpenGL ES 는 3.1부터 이 연산을 바로 지원하도록 하고 있다. 이 전까지는 뭔가 그리는 것으로 작업 끝내는 용도였지만, 이 제 연산만 하고 다시 CPU에서 끌어다 쓸 수 있게 지원을 해준다. 그 것이 ComputeShader라는 녀석이다.

3.0이하 버전에서도 약간의 트릭을 써서 만들 수는 있다. Fragment shader로 그려서 FBO에 결과를 다시 읽어오면 같은 결과를 만들어 낼 수 있기 때문이다.

 

Header와 Link

Include할 파일은 #include <GLES3/gl31.h> 이며 link는 GLESv3와 하면 된다.

app을 컴파일하는 중에 undefined reference에러를 보게 된다면, minSdkVersion을 맞추어 주거나 eglGetProcAddress함수를 통하여 함수 포인터를 이용하여 호출을 해야 한다.

eglGetProcAdress를 이용하여 함수의 포인터를 얻는 방법은 다음의 글을 참고하면 된다.

https://jamssoft.tistory.com/236

 

Android OpenGL ES 버전 호환성 맞추기

안드로이드의 최소버전(minSdkVersion)을 기준으로 네이티브 코드는 링크가 된다.[*1], 그러다 보니 이 minSdkVersion을 강제로 올려야 되는 상황이 발생한다. 만들고 있는 앱을 지원하지 않는 기능을 제

jamssoft.tistory.com

 

ComputeShader지원 여부확인

ComputeShader는 OpenGL ES 3.1부터 지원한다. 따라서 기기가 3.1이상을 지원해야만 사용할 수 있다.

GL_VERSION을 이용하여 OpenGL ES 버전을 확인해서 지원유무를 판단하면 된다. 위의 Header와 Link에 있는 링크를 확인해 보면 OpenGL ES 버전을 가지고 오는 함수가 있다. 이 함수를 이용하여 버전정보를 가지고 와서 다음과 같이 확인하면 된다.

function IsComputeShaderSupport() {
    GLint glMajorVer, glMinorVer;
    if( !GetGLVersion( &glMajorVer, &glMinorVer ) ) {
        LOGE( TAG, "GLGetVersion Failure" );
        return false; // GL Context가 없는 듯..
    }
    // 3.1 이상인지 확인
    if( glMajorVer > 3 || ( glMajorVer == 3 && glMinorVer >= 1 ) ) {
        return true;// ComputeShader 지원
    }
    // 3.1이하 지원안함
    return false;
}

가끔 CPU-Z와 같은 앱으로 확인해보면 버전이 3.1인 경우가 있지만 위의 함수를 돌려보면 3.0으로 나오는 경우가 있다. 이는 CPU-Z와 같은 앱이 해당 SoC의 정보를 그냥 보여주기 때문이다. 실제 위의 함수에서 3.0이 나온다면 그 기기는 드라이버가 3.1을 지원하지 않는 것이기 때문에 ComputeShader를 쓸 수 없다. 꼭 GL_VERSION을 확인하여 지원여부를 확인해야 한다. 또하나 더 이야기 하면 안드로이드 버전이 아무리 높다고 해도 SoC가 지원하지 않는 경우도 지원할 방법이 없다.

ARB_compute_shader확장을 통해서 지원하는 기기들이 있을 듯 한데.. 필자가 그런 기기를 보지 못해서...

 

Kernel이란?

공부와 동시에 몇가지 단어를 좀 정리하고 넘어갈 필요가 있다.

Cpu를 이용하여 다음과 같은 작업을 한다고 치자.

for( int i = 0 ; i < 1024 ; i++ ) {
      함수(i);
}

GPU를 이용할 경우 위의 루프를 동시에 1024개 쓰레드로 분리하여 실행을 하게 될 것이다. 고로, 우리는 이 ComputeShader를 이용할 경우 루프내의 "함수(i)"만 구현하면 된다. 이 함수를 이 컴퓨트셰이더에서는 Kernel이라고 부른다.

루프는 ComputeShader의 사용방법을 통하여 지정할 수 가 있다.

 

 

WorkGroup

ComputeShader는 WorkGroup이라는 단위를 사용한다. 한 WorkGroup에는 여러개의 Thread가 존재할 수가 있다.

실제 실행을 할 때는, 한 WorkGroup내에 몇개의 Thread를 실행 시킬지와 총 몇 개의 WorkGroup을 만들지 두 단계로 만들어 두었다. 따라서 총 실행될 Thread 개수(이 모든 Thread가 동시에 실행되지는 않는다.."실행[될]")는

 

총 실행될 Thread 개수 = 한 WorkGroup내의 Thread * 총 WorkGroup수

 

이렇게 두단계로 분리를 한 이유가 있는 데, 일단 GPU가 아무리 Thread를 많이 생성할 수 있어도 하드웨어상의 한계가 있다. 이 것이 하나의 이유고, 또 하나의 관련된 중요한 이유는 한 WorkGroup내의 Thread는 동시에 실행된다는 규칙이 있다. 정확히 동시에 실행이 되지 않아도 동시에 실행되는 것과 동일한 결과를 만들어내야 한다는 규칙이다. 이는 드라이버를 만드는 사람들의 일이니 필자는 신경 안 쓸 거다. 우리는 동시에 실행된다는 가정하여 그냥 코딩하면 된다.

한 워크그룹내의 Thread들은 Shared Memory를 이용하여 서로 데이터를 공유할 수 있고, Thread들간의 Synchronizing을 지원한다. 워크그룹들간은 이 기능을 지원하지 않는 것 같다.

 

위의 내용을 CPU코드로 만들면

// 외부루프는 동시에 실행이 될 수도 있고 아닐 수도 있다. 별로 안 중요하다.
for( int i = 0 ; i < 워크그룹수 ; i++ ) {
   
   // 내부루프의 쓰레드는 꼭 동시에 실행된다고 본다.(가정한다)
   for( int j = 0 ; j < [워크그룹내의 쓰레드수] ; j++ ) {
         커널함수( i, j ); //
   }
}

위 와 같이 된다.

 

[워크그룹내의 쓰레드수]는 GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS[glGetInteger*]값을 통해서 알아 낼 수 있다. 이 값은 최소 128로 되어있다.

최대 워크그룹수는 GL_MAX_COMPUTE_WORK_GROUP_COUNT로 구할 수 있다. 최소값이 65535 였든가??

 

 

그냥 한 번 보기만.. 다른 내용에서 비슷한 내용을 다루게 될 것이다.

대부분의 Android기기내의 GPU는 동시에 실행되는 Thread가 그리 많지 않다. 또한 모바일 칩에다가 이 걸 다 때려 넣어놓기란 그렇게 쉽지 않았을 것이다. 그래서 좀 특이한 방식을 쓰는 데, 그 중에 하나는 가변형 Thread이다. 이는 GPU에서 만들어 낼 수 있는 Thread수를 알아서 증가하는 방식이다. 갤럭시S7[엑스노스판]의 경우 Mali-T880 GPU를 사용하는 데, 이 GPU가 동시에 돌릴 수 있는 최대 Thread 개수는 16개다(1~16개 까지 알아서 늘었다 줄었다 한다.) 하지만 OpenGL ES 3.1의 스펙에는 최소 128개를 돌려야 하기 때문에, 연산을 위해 register가 아닌 stack에서 연산을 해버린다.  또한 register를 나눠서 사용하는 방식의 Thread를 생성하기 때문에  16개가 전부 생성된다는 보장도 없다. 이는 가변형이 아닌 고정형이라고 같은 문제를 안고 있다. 따라서 몇개의 Thread를 돌릴지 잘 판단하는 것이 속도에서는 중요한 문제다.

WorkGroup

위의 그림은 2차원만 그렸을 뿐, 3차원을 다루는 GPU답게 3차원방식으로 지원을 해준다.

 

이제 개념을 잡았으니, 실전코드로 점점 들어가 보자.

 

다음편에 계속..

Replies
Reply Write