Quick Syntax
#version 430 core
layout(local_size_x = 64) in;
layout(std430, binding = 0) buffer Values {
float values[];
};
uniform uint count;
void main() {
uint i = gl_GlobalInvocationID.x;
if (i >= count) return;
values[i] *= 2.0;
}GL 4.3 Core의 예입니다. count개 원소를 처리할 그룹 수는 64개 단위로 올림하고, 초과 invocation은 범위 검사로 제외합니다. 이 예제는 그룹 합류 barrier가 없어 early return을 사용할 수 있습니다.
CPU 연결
SSBO에 count개 float가 준비되어 있고 compute 전용 프로그램이 링크되어 있다는 전제입니다. count 0이면 이 예제의 dispatch를 건너뜁니다. 계산의 overflow가 없는 데이터 크기로 그룹 수를 정합니다.
glUseProgram(computeProgram);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, buffer);
glUniform1ui(glGetUniformLocation(computeProgram, "count"), count);
GLuint groups = count / 64 + (count % 64 != 0);
if (groups > 0) glDispatchCompute(groups, 1, 1);
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);마지막 barrier는 다음 GPU 명령이 SSBO로 읽는 경우입니다. CPU 읽기·정점 입력·간접 명령이 소비자라면 해당 접근 비트와 완료 조건을 선택합니다. 이 호출만으로 CPU가 결과를 즉시 읽어도 된다는 뜻은 아닙니다.
입력 데이터와 지원 한도
입력 float 네 개를 처음 만드는 경우의 저장소는 다음과 같습니다. 이 초기화 후 앞의 dispatch 코드에서 같은 buffer와 count를 사용합니다. 위 셰이더의 결과는 원소별 두 배인 [2,4,6,8]이며 실행 결과 보고가 아닌 코드가 정의한 계산입니다.
const GLfloat input[] = {1, 2, 3, 4};
const GLuint count = 4;
GLuint buffer = 0;
glGenBuffers(1, &buffer);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer);
glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(input), input, GL_DYNAMIC_DRAW);
// 이후 dispatch와 소비가 끝나면 glDeleteBuffers(1, &buffer);활성 compute 프로그램의 local size와 구현 한도는 서로 다른 조회입니다. program이 링크에 성공한 GL 4.3 컨텍스트에서 사용합니다.
GLint maxGroups[3] = {}, maxLocal[3] = {}, local[3] = {};
GLint maxInvocations = 0, maxSharedBytes = 0;
for (GLuint axis = 0; axis < 3; ++axis) {
glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, axis, &maxGroups[axis]);
glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_SIZE, axis, &maxLocal[axis]);
}
glGetIntegerv(GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS, &maxInvocations);
glGetIntegerv(GL_MAX_COMPUTE_SHARED_MEMORY_SIZE, &maxSharedBytes);
glGetProgramiv(computeProgram, GL_COMPUTE_WORK_GROUP_SIZE, local);그룹 개수는 각 축의 최대값 이하여야 합니다. 축 하나가 0이면 작업 그룹이 실행되지 않습니다. local size는 축별 한도와 세 축 곱의 invocation 한도를 모두 만족해야 하고, shared 저장소 합계는 바이트 한도 이내여야 합니다. 현재 compute 프로그램이 없으면 dispatch는 GL_INVALID_OPERATION입니다.
그룹과 메모리
| 값 | 의미 | 확인할 한도 |
|---|---|---|
| local_size | 그룹 안 invocation 배치 | 축별 크기·총 invocation 최대 |
| group count | dispatch할 그룹 개수 | 축별 최대 group count |
| GlobalInvocationID | 전체 범위의 ID | 실제 데이터 count와 별도 |
| LocalInvocationID | 그룹 안 좌표 | shared 배열 인덱스 |
| shared memory | 그룹 안 공유 저장 | 그룹별 용량과 초기화 책임 |
shared 저장소는 자동으로 의미 있는 초기값을 갖지 않습니다. 필요한 원소를 먼저 쓰고, 그룹의 다른 invocation이 읽기 전에 적절한 동기화를 합니다. barrier()는 그룹의 실행 합류이며 모든 필요한 invocation이 같은 합류 지점에 도달하도록 구성합니다. 조건부 return으로 일부만 빠져나가고 나머지가 barrier에서 기다리는 코드는 위의 독립 원소 예제와 다릅니다.
그룹 사이의 순서
서로 다른 그룹의 실행 순서를 번호로 가정하지 않습니다. 그룹 전체가 기다리는 장치 전역 barrier를 한 dispatch 안에서 스핀 루프로 흉내 내면 교착 위험이 있습니다. 전체 단계가 끝난 결과를 다음 단계가 읽어야 한다면 dispatch를 나누고 API memory barrier를 사용합니다.
선택과 버전
Compute는 4.3 코어 경로입니다. 3.3에서 동일한 API가 있다는 전제로 문서를 쓰지 않습니다. 독립 입출력·병렬 작업의 크기·데이터 이동 비용을 보고 CPU·TF·프래그먼트 처리 대안과 비교합니다. 저장소 접근과 동기화가 자원·실행 조건을 나누어 담당합니다.
참고 링크
4 sources