Tistory View
Android GPU로 계산하기 : ComputeShader #2 컴파일/기본예제
What should I do? 2021. 6. 29. 17:04ComputeShader는 한개의 쉐이더만 있으면 된다. 다음은 ComputeShader를 컴파일하는 함수다.
GLuint CompileComputeShader( const char* szShaderCode ) {
GLuint rc = 0;
int rvalue;
GLchar log[1024];
GLsizei length;
GLuint program = glCreateProgram();
GLuint shader = glCreateShader(GL_COMPUTE_SHADER);
glShaderSource( shader, 1, &szShaderCode, nullptr );
glCompileShader(shader);
glGetShaderiv(shader, GL_COMPILE_STATUS, &rvalue );
if (!rvalue) {
length = sizeof(log);
glGetShaderInfoLog( shader, sizeof(log), &length, log);
LOGD( TAG, "Error: Compiler log:\n%s\n", log);
goto last;
}
glAttachShader(program, shader);
glLinkProgram(program);
glGetProgramiv( program, GL_LINK_STATUS, &rvalue);
if ( !rvalue ) {
length = sizeof(log);
glGetProgramInfoLog( program, sizeof(log), &length, log);
LOGD( TAG, "Error: Linker log:\n%s\n", log);
goto last;
}
rc = program;
program = 0;
last:
if( program ) {
glDeleteProgram( program );
}
if( shader ) {
glDeleteShader( shader );
}
return rc;
}
파라미터로 쉐이더코드를 넘겨주면 알아서 쉐이더 프로그램을 만들어 준다.
다음은 우리가 초등학교 때 혼나기 싫어서 신나게 외웠던 구구단을 만들어내는 쉐이더 코드다.
#version 310 es
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1 ) in;
layout(binding = 0 ) writeonly buffer Ninenine {
uvec4 e[];
} ninenine;
void main() {
uint idx = gl_WorkGroupID.y * gl_NumWorkGroups.x + gl_WorkGroupID.x;
ninenine.e[idx] = uvec4( gl_WorkGroupID.y, gl_WorkGroupID.x, gl_WorkGroupID.y * gl_WorkGroupID.x, idx );
}
마지막으로 구구단을 GPU에서 처리하고 출력하는 코드다.
struct vec4 {
GLuint x;
GLuint y;
GLuint z;
GLuint w;
};
const char* shaderCode = "~~~~~구구단 쉐이더 코드~~~~~";
const int MAX_DAN = 10; // 0 ~ 9
const int MAX_OPR = 10; // 0 ~ 9
const int NINExNINE_SIZE = MAX_DAN * MAX_OPR * sizeof( vec4 );
GLuint prg = 0;
GLuint ssbo = 0;
vec4* pBuf = nullptr;
char buf[32];
std::string res;
if( !prg ) {
prg = CompileComputeShader(shaderCode);
}
if( !ssbo ) {
glGenBuffers( 1, &ssbo );
assert( ssbo );
glBindBuffer( GL_SHADER_STORAGE_BUFFER, ssbo );
glBufferData( GL_SHADER_STORAGE_BUFFER, NINExNINE_SIZE, nullptr, GL_STREAM_DRAW );
glBindBuffer( GL_SHADER_STORAGE_BUFFER, 0 );
}
if( prg ) {
glUseProgram( prg );
CGles2::BindBufferBase( GL_SHADER_STORAGE_BUFFER, 0, ssbo );
CGles2::DispatchCompute(MAX_OPR, MAX_DAN, 1 );
glUseProgram( 0 );
glMemoryBarrier( GL_ALL_BARRIER_BITS ); // 필요없나???
glBindBuffer( GL_SHADER_STORAGE_BUFFER, ssbo );
pBuf = (vec4*)glMapBufferRange( GL_SHADER_STORAGE_BUFFER, 0, NINExNINE_SIZE, GL_MAP_READ_BIT );
if( pBuf ) {
for(int i = 0 ; i < MAX_OPR ; i++ ) {
for(int j = 0 ; j < MAX_DAN ; j++ ) {
sprintf( buf, "[%3d]%2dx%2d=%2d ",
(int)pBuf[j * MAX_OPR + i ].w,
(int)pBuf[j * MAX_OPR + i ].x,
(int)pBuf[j * MAX_OPR + i ].y,
(int)pBuf[j * MAX_OPR + i ].z
);
res += buf;
}
LOGD( TAG, "\n%s", res.c_str() );
res.clear();
}
} else {
res = "MapBufferRange Failure";
}
} else {
res = "nine x nine program not exists";
}
if( pBuf ) {
glUnmapBuffer( GL_SHADER_STORAGE_BUFFER );
pBuf = nullptr;
}
이제 하나하나 뜯어 보자.
#version 310 es
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1 ) in;
3.1버전임을 명시하고, local_size를 지정한다. local_size는 이전 글에서 말한 WorkGroup내의 Thread수를 지정하는 것이다. 워크그룹내의 총 쓰레드수는 local_size_x * local_size_y * local_size_z 이렇게 계산 된다.
이 구구단예제는 Workgroup내에 1개의 Thread만을 사용하고 있다.
layout(binding = 0 ) writeonly buffer Ninenine {
uvec4 e[];
} ninenine;
구구단의 결과를 저장할 버퍼를 지정하는 것이다. 이는 C코드에서 다음 부분에 해당한다.
glBindBufferBase( GL_SHADER_STORAGE_BUFFER, 0, ssbo );
두번째 파라미터인 "0"이 binding=0에 해당하는 값이다.
접근은 ninenine.e[인덱스]로 접근을 하면 된다.
void main() {
uint idx = gl_WorkGroupID.y * gl_NumWorkGroups.x + gl_WorkGroupID.x;
ninenine.e[idx] = uvec4( gl_WorkGroupID.y, gl_WorkGroupID.x, gl_WorkGroupID.y * gl_WorkGroupID.x, idx );
}
실제 쉐이더의 Kernel코드는 main함수로 지정한다.
ninenine버퍼에 해당하는 인덱스를 구하고 구구단을 만들어 저장을 한다.
gl_WorkGroupID는 C코드의 glDispatchCompute에서 넘긴 값에 해당하는 WorkGroup의 ID이다.
C코드에서 glDispatchCompute( 10, 10, 1 )로 호출하게 되는 데, 넘기는 값은 호출할 개수라
gl_WorkGroupID.x = 0~9의 값을 가지게 되고(y도 마찬가지)
gl_WorkGroupID.z = 0~0의 값을 가지게 된다.
gl_NumWorkGroups가 glDispatchCompute에 넘긴 x=10, y=10, z=1을 가지고 있는 변수다.
이제 예제를 하나 해 봤으니 자세한 설명을 하겠다.
"WorkGroup내의 Thread 수"와 "WorkGroup수" 지정
WorkGroup내의 Thread 수는 아예 Shader코드안에 다음과 같이 들어간다.
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1 ) in;
위 코드는 Thread수를 8x8x1=64개를 사용하겠다는 의미다.
3차원방식이라 x,y,z를 따로 지정할 수 있고, x,y의 최대값은 GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS으로 확인 할 수 있다. z값은 GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS 값의 반이다. GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS는 최소 128이다.
GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS값이 128이라면
local_size_x : 1 ~ 128
local_size_y : 1 ~ 128
local_size_z : 1 ~ 64
까지만 쓸 수 있다.
또한 이 GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS값은 Workgroup내의 전체 Thread수를 나타내는 것이므로
local_size_x * local_size_y * local_size_z 또한 GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS 값을 넘으면 안된다.
local_size_x * local_size_y * local_size_z <= GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS
local_size_*값이 1인경우는 그냥 생락할 수도 있다.[하지만 최소한 아무 것이나 한개는 지정을 해줘야 할 듯]
따라서 위의 코드는 다음과 같이 바꿀 수 있다.
layout(local_size_x = 8, local_size_y = 8 ) in;
WorkGroup수는 glDispatchCompute의 파라미터로 넘긴다.
glDispatchCompute( 8, 9, 10 );
총 WorkGroup수는 [8 * 9 * 10 = 720]번 이 되고 WorkGroup
내의 Thread는 [8*8*1=64]번이 된다.
위와 같이 호출을 하면 총 Thread수는 다음과 같다.
( 8 * 9 * 10 ) * ( 8 * 8 * 1 ) = 46080
혹시나 하나라도 0넣었다가는 하나도 실행 안된다.
쉐이더 내에서 어떤 WorkGroup에 속한 어떤 Thread인지 구분하기 위해 기본적으로 여러 변수를 넣어놨다.
uvec3 gl_NumWorkGroups | WorkGroup수 glDispatch의 파라미터에 해당하는 값 | 예에서 xyz=>8, 9, 10 |
uvec3 gl_WorkGroupSize | WorkGroup내의 Thread수 | 예에서 xyz=>8, 8, 1 |
uvec3 gl_LocalInvocationID | WorkGroup내의 Thread ID 값 | 예에서 x : 0~7[8] y : 0~7[8] z : 0~0[1] |
uvec3 gl_WorkGroupID | WorkGroup ID | 예에서 x : 0~7[8] y : 0~8[9] z : 0~9[10] |
uint gl_LocalInvocationIndex | WorkGroup내의 Thread ID값을 1차원값(단순히 0~최대값) | 예에서 0~63(8x8) |
uvec3 gl_GlobalInvocationID | 전체의 ID값 gl_WorkGroupID * gl_WorkGroupSize + gl_LocalInvocationID을 계산해 놓은 값 |
예에서 (0~7) x 8 + (0~7) = ( 0 ~ 63) (0~8) x 8 + (0~7) = ( 0 ~ 71) (0~9) x 1 + (0~0) = ( 0 ~ 9) 이거 테스트 안함 |
이렇게 어떤 Id에 해당하는 지를 구할 수 있다.
전체ID값의 1차원형은 지원해 주지 않는 것 같다. 이 계산은 독자들의 숙제로 남겨 두겠다.
'Android Develop > OpenGLES' 카테고리의 다른 글
Android GPU로 계산하기 : ComputeShader #5 Barrier기본, API Barrier (0) | 2021.07.02 |
---|---|
Android GPU로 계산하기 : ComputeShader #4 Texture다루기 (0) | 2021.07.02 |
Android OpenGL ES 버전 호환성 맞추기 (0) | 2021.06.26 |
Android GPU로 계산하기 : ComputeShader #1 소개 (0) | 2021.06.26 |
OpenGL ES : (Texture or FBO) to (Texture or FBO) 복사 정리 (0) | 2021.06.21 |
- Total
- Today
- Yesterday
- 에어컨
- 블로그
- 전기요금
- ComputeShader
- 애드센스
- 안드로이드
- 애드핏
- TTS
- 적금
- Android
- 컴퓨트셰이더
- 재테크
- 티스토리
- 예금
- gpgpu
- OpenGL ES
- 컴퓨트쉐이더
- texture
- 재태크
- 에어콘
- 금리
- 사용료
- 경제보복
- 전기료
- OpenGLes
- choreographer
- 공유 컨텍스트
- 전기세
- 텍스처
- 아끼는 법
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |