Quick Comparison
읽기 전용 상수는 UBO, 구조화된 읽기·쓰기는 SSBO, 픽셀 단위 접근은 image, 공유 정수의 경합 처리는 atomic을 선택합니다. 원자적 연산은 해당 연산의 충돌을 처리하며 다른 메모리와 단계의 순서는 별도로 맞춥니다.
| 저장소 | 접근 형태 | 기준 버전 |
|---|---|---|
| UBO | 구조화된 읽기 전용 상수 | 3.1 |
| SSBO | 버퍼의 구조화된 읽기·쓰기 | 4.3 |
| image | texel 단위 load/store·허용 atomic | 4.2 |
| atomic counter | 전용 카운터 연산 | 4.2 |
SSBO 레이아웃
#version 430 core
layout(std430, binding = 0) buffer Particles {
vec4 positions[];
};이 블록은 마지막의 런타임 배열을 포함할 수 있습니다. 실제 길이는 바인딩한 저장소 범위와 stride에 달려 있으며 셰이더의 접근 범위가 맞아야 합니다. std430은 std140보다 일부 배열·구조체 패딩을 줄이지만 모든 타입이 빈틈없이 C++ 구조체와 같아지는 것은 아닙니다. 특히 vec3와 행렬의 정렬·stride는 확인합니다.
CPU는 glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, buffer) 또는 Range로 연결합니다. Range offset은 SSBO 정렬 한도를 따르고, 블록 최대 크기와 단계별 binding 한도를 확인합니다.
버퍼 내용부터 연결하기
positions에 정점 두 개를 넣는 GL 4.3 초기화 구간입니다. 각 vec4는 float 네 개, 16바이트로 연속 저장합니다. 생성한 버퍼는 이 데이터를 사용하는 마지막 GPU 명령을 제출한 뒤 소유권 정책에 맞춰 삭제합니다.
const GLfloat positions[] = {0, 0, 0, 1, 1, 0, 0, 1};
GLuint particles = 0;
glGenBuffers(1, &particles);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, particles);
glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(positions), positions, GL_DYNAMIC_DRAW);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, particles);
// 마지막 사용 뒤 glDeleteBuffers(1, &particles);std430의 vec4 배열 stride는 16바이트입니다. 스칼라 float 배열은 4바이트 stride를 사용할 수 있지만 vec3 배열은 12바이트 밀집 배열이라고 가정하면 안 됩니다. 길이 2인 저장소를 연결했다면 positions.length()는 2이고, 인덱스 2는 이미 범위 밖입니다.
Image 연결
// GL 4.2 이상: texture는 GL_RGBA32F 저장소의 2D 텍스처입니다.
glBindImageTexture(1, texture, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA32F);GLSL은 layout(rgba32f, binding = 1) uniform image2D target;처럼 맞춥니다. image unit 1은 sampler의 텍스처 유닛 1과 별개 바인딩 공간입니다. 위의 layered=false는 단일 레이어 접근이며 배열·3D·큐브 데이터에서는 대상 layer·layered 조건을 명확히 합니다.
| 조건 | 의미와 실수 |
|---|---|
| format qualifier | 이미지 저장소의 허용 호환 형식과 맞춤 |
| access READ/WRITE/READ_WRITE | glBindImageTexture로 지정한 셰이더 접근 계약과 실제 읽기·쓰기 일치 |
| readonly/writeonly | 사용 의도를 제한, 자동 동기화는 아님 |
| coherent | 가시성 규칙에 관여, 실행 합류·완료와 별개 |
| restrict | 별칭이 없다는 약속, 거짓 약속은 최적화 오류 원인 |
imageLoad/imageStore는 일반 sampler 필터링이 아니라 정수 texel 좌표 접근입니다. 정규화 UV를 넣거나 선형 필터가 적용된다고 기대하지 않습니다.
Image에 쓰는 최소 셰이더
다음은 GL 4.3 / GLSL 430 compute 셰이더입니다. 앞서 설명한 image API 자체는 4.2부터지만 compute 단계와 함께 쓰므로 예제의 기준은 4.3입니다.
#version 430 core
layout(local_size_x = 8, local_size_y = 8) in;
layout(rgba32f, binding = 1) uniform writeonly image2D target;
void main() {
ivec2 p = ivec2(gl_GlobalInvocationID.xy);
ivec2 size = imageSize(target);
if (any(greaterThanEqual(p, size))) return;
imageStore(target, p, vec4(vec2(p) / vec2(size), 0.0, 1.0));
}양수인 width·height 크기의 출력과 이 소스를 링크한 imageProgram을 준비합니다. 아래 코드는 대상 이미지를 만드는 구간부터 시작합니다. compute가 쓴 결과를 다음 draw에서 일반 sampler2D로 읽으므로 TEXTURE_FETCH barrier를 선택합니다.
GLuint output = 0;
glGenTextures(1, &output);
glBindTexture(GL_TEXTURE_2D, output);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA32F, width, height);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glBindImageTexture(1, output, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA32F);
glUseProgram(imageProgram);
glDispatchCompute(width / 8 + (width % 8 != 0),
height / 8 + (height % 8 != 0), 1);
glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT);
// 이후 일반 텍스처 유닛에 output을 연결하고 sampler2D로 읽습니다.
// 마지막 사용 뒤 image/texture 연결을 정리하고 glDeleteTextures(1, &output);unit은 0부터 GL_MAX_IMAGE_UNITS 미만이며, compute 단계의 image 한도도 충족해야 합니다. level은 존재하는 밉 레벨을 고릅니다. layered = GL_TRUE로 지원되는 레이어 전체를 연결하면 layer 인수는 무시됩니다. WRITE_ONLY 바인딩에서 imageLoad를 실행하는 등 접근 권한을 어기면 단순한 반환 오류로 안전하게 처리된다고 기대할 수 없습니다.
원자적 연산과 소비자
여러 invocation이 하나의 카운터를 갱신할 때 atomicAdd 같은 연산을 사용할 수 있습니다. 지원되는 타입·저장소·함수를 확인합니다. 예를 들어 원자적으로 슬롯 번호를 얻었다고 그 슬롯의 다른 데이터 기록까지 동시에 소비자에게 보이는 것은 아닙니다. 별도 쓰기·읽기의 가시성·실행 순서가 필요합니다.
다음 소비자가 image인지 sampler인지 SSBO인지에 따라 memory barrier를 선택합니다. 일반 FBO 색 기록과 image store를 같은 규칙의 출력으로 처리하지 않습니다.
Atomic으로 공유 카운터 증가
GL 4.3에서 invocation 64개가 같은 uint를 한 번씩 증가시키는 예입니다. 별도 atomic_uint 저장소가 아니라 SSBO 멤버에 대한 atomicAdd이며, 두 방식을 혼동하지 않습니다.
#version 430 core
layout(local_size_x = 64) in;
layout(std430, binding = 2) buffer Counters {
uint hits;
};
void main() {
atomicAdd(hits, 1u);
}// 위 셰이더를 링크한 counterProgram과 GL 4.3 컨텍스트를 사용합니다.
const GLuint zero = 0;
GLuint counter = 0;
glGenBuffers(1, &counter);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, counter);
glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(zero), &zero, GL_DYNAMIC_DRAW);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, counter);
glUseProgram(counterProgram);
glDispatchCompute(1, 1, 1);
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
// 다음 GPU 명령이 SSBO로 읽는 hits의 값은 64입니다.
// CPU로 읽으려면 해당 읽기 경로의 가시성·완료 조건이 추가로 필요합니다.hits += 1u로 바꾸면 읽기·수정·쓰기가 겹쳐 증가가 유실될 수 있습니다. 카운터의 초기 0은 명시적으로 업로드한 값이며 자동 기본값이 아닙니다. 다음 누적 회차 전에 0으로 되돌릴지 누적할지 정하고, 이전 사용이 끝나기 전에 같은 저장소를 덮지 않습니다. 마지막 사용 뒤 counter를 삭제합니다.
image atomic의 기본 정수 경로는 r32i/r32ui 형식 등 허용 조건이 있으므로 RGBA32F 예제에 imageAtomicAdd를 그대로 붙이지 않습니다. atomic_uint는 GL_ATOMIC_COUNTER_BUFFER 바인딩과 4바이트 단위 offset을 사용하는 별도 카운터입니다. 원자 연산 하나가 다른 일반 메모리 쓰기까지 한 번에 공개하지는 않습니다.
버전 경계
image load/store와 atomic counter는 4.2, SSBO는 4.3 코어 기능입니다. atomic의 연산 종류가 모든 버전에서 같은 것은 아니므로 사용하는 함수의 지원을 확인합니다. 3.3 기본 독자는 UBO·텍스처 읽기 경로와 차이를 비교하고 해당 기능을 쓰려면 환경 기준을 올려야 합니다.
참고 링크
6 sources