Quick Comparison
선택 기준은 갱신량·빈도·소비 시점입니다. UNSYNCHRONIZED나 COHERENT가 안전한 덮어쓰기를 대신하지는 않습니다.
| 방식 | 기본 조건 | 책임 |
|---|---|---|
| SubData | 기존 저장소 내부 갱신 | 아직 GPU가 읽는 범위면 대기 가능 |
| orphaning | 가변 저장소를 다시 요청 | 이전 데이터 보존하지 않음 |
| 다중·링 버퍼 | 여러 구간을 번갈아 사용 | 구간 재사용 전 완료 확인 |
| persistent mapping | 4.4 또는 대응 확장 | 가시성과 GPU 완료를 별도로 관리 |
3.3의 기본 경로
// GL 3.3 Core, 가변 buffer 저장소와 새 데이터가 준비되어 있습니다.
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, capacityBytes, nullptr, GL_STREAM_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, dataBytes, data);새 저장소를 요청하고 필요한 영역을 채우는 형태입니다. dataBytes는 capacityBytes 이내여야 하며 업로드하지 않은 영역의 값은 정의되지 않습니다. 드라이버가 언제나 물리 메모리를 새로 할당하거나 항상 대기를 없앤다고 보장하지 않습니다.
범위 매핑에서는 WRITE와 필요에 맞는 invalidate 플래그를 사용합니다. 반환 포인터를 확인하고 쓰기 범위가 맞아야 합니다. UNSYNCHRONIZED는 앱이 충돌을 피했다는 책임을 지는 선택이며, GPU가 읽는 구간을 덮으면 결과가 잘못될 수 있습니다.
구간 재사용
링 버퍼는 이번 프레임 쓰기 구간과 이전 프레임 GPU 읽기 구간을 분리합니다. 프레임 번호가 세 번 지났다는 이유만으로 완료를 보장하지 않습니다. 마지막 소비 뒤 fence를 기록하고 구간을 다시 쓰기 전에 완료를 확인합니다. 미완료이면 다른 구간·증설·대기 중 명시적인 정책을 택합니다.
정점 입력·UBO 등 소비 방식마다 바인딩 정렬과 최소 크기가 다릅니다. UBO의 offset alignment와 내부 std140 패딩은 별개 조건입니다.
영속 매핑
4.4 BufferStorage에 MAP_WRITE·MAP_PERSISTENT 등 필요한 flags를 지정하고, 매핑 시에도 호환되는 접근 플래그를 사용합니다. COHERENT 경로는 가시성 처리를 단순하게 만들지만 GPU가 이전 데이터를 읽는 중인지에 대한 완료 문제는 남습니다.
비일관 영속 쓰기 경로는 explicit flush를 사용하는 경우 수정 범위를 glFlushMappedBufferRange로 공개합니다. FLUSH_EXPLICIT는 저장소 flags가 아니라 매핑 access의 옵션입니다. 저장소 생성과 매핑 플래그를 같은 비트 집합으로 복사하지 않습니다. CPU가 GPU 결과를 읽는 방향에서는 비일관 영속 매핑에 GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT를 적용한 뒤 fence의 완료를 확인합니다. coherent 매핑도 완료 대기는 필요합니다. 이 순서는 GPU→CPU 가시성에 정리합니다.
영속 쓰기의 플래그 조합
한 번 매핑한 주소를 유지하는 GL 4.4 초기화 예입니다. capacityBytes는 양수이고, 기존 저장소가 없는 새 버퍼를 사용합니다. 앞의 3.3 orphaning 예제와 대체 관계이므로 같은 버퍼에 이어서 적용하지 않습니다.
GLuint stream = 0;
glGenBuffers(1, &stream);
glBindBuffer(GL_ARRAY_BUFFER, stream);
GLbitfield storageFlags = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT;
glBufferStorage(GL_ARRAY_BUFFER, capacityBytes, nullptr, storageFlags);
GLbitfield mapFlags = storageFlags | GL_MAP_FLUSH_EXPLICIT_BIT;
void* mapped = glMapBufferRange(GL_ARRAY_BUFFER, 0, capacityBytes, mapFlags);
if (mapped == nullptr) {
glDeleteBuffers(1, &stream);
stream = 0;
}성공한 뒤 CPU가 소유하고 있는 [offset, offset + bytes)만 씁니다. 여기서 offset·bytes는 전체 매핑 안에 있고, 이전 GPU 소비가 끝났음이 확인된 구간입니다.
std::memcpy(static_cast<unsigned char*>(mapped) + offset, data, bytes);
glFlushMappedBufferRange(GL_ARRAY_BUFFER, offset, bytes);
// 이 구간을 참조하도록 VAO/범위를 연결하고 draw를 제출합니다.
// 마지막 소비 뒤 fence를 넣고 재사용 시 완료를 확인합니다.flush의 offset은 버퍼 전체가 아니라 매핑 시작 기준입니다. 이 예제는 전체를 offset 0에서 매핑했으므로 둘이 같습니다. 다른 GL 호출로 ARRAY_BUFFER 바인딩을 바꿨다면 flush 전에 stream을 다시 바인딩합니다.
| 경로 | 저장소 flags | 매핑 access | CPU 쓰기 공개 |
|---|---|---|---|
| 비일관 영속 쓰기 | WRITE + PERSISTENT | WRITE + PERSISTENT + FLUSH_EXPLICIT | 바뀐 구간을 flush |
| 일관 영속 쓰기 | WRITE + PERSISTENT + COHERENT | 같은 세 비트 | 같은 CPU 스레드에서 이후 GL 소비 전에 쓰기 완료 |
COHERENT에는 PERSISTENT가 필요합니다. 표의 이름은 GL_MAP_*_BIT 비트를 줄여 쓴 것입니다. DYNAMIC_STORAGE_BIT는 SubData 갱신 허용과 관련되며 위의 매핑 쓰기에 필수인 비트는 아닙니다. 종료 시 더 이상 CPU/GPU가 매핑을 사용하지 않도록 정리한 뒤 unmap·삭제합니다.
실패와 버전
가변 저장소의 orphaning과 불변 저장소의 persistent mapping을 섞어 불변 버퍼에 BufferData를 호출하지 않습니다. 매핑 주소를 저장소 재생성 후에도 계속 사용하지 않습니다. 3.3 기본 예제는 일반 갱신·매핑·sync로 설명하고, 4.4 이후 경로는 별도 조건을 표시합니다. 완료 대기와 함께 읽어야 메모리 가시성과 실행 완료를 혼동하지 않습니다.
참고 링크
4 sources