Quick Syntax
// GL 3.3 Core: 읽을 framebuffer·read buffer가 선택되어 있습니다.
// GL_PIXEL_PACK_BUFFER는 0, rgba는 width*height*4 바이트 이상입니다.
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, rgba);CPU 주소를 넘기는 읽기와 PBO 오프셋을 넘기는 읽기는 같은 마지막 인수를 다르게 해석합니다. PBO를 사용해도 즉시 매핑하면 대기가 발생할 수 있습니다.
읽기 상태
| 항목 | 기본·단위 | 주의할 조건 |
|---|---|---|
| READ_FRAMEBUFFER | 초기 기본 framebuffer | draw framebuffer와 다른 대상일 수 있음 |
| read buffer | 대상의 색 소스 선택 | 단일 버퍼 창은 FRONT, 이중 버퍼 창은 BACK; 사용자 FBO는 COLOR_ATTACHMENT0 등 |
| x/y/width/height | 픽셀 좌표·크기 | 대상 크기와 범위 확인 |
| format/type | CPU 출력 형태 | 정수·깊이 등 저장 형식과 허용 조합 |
| PACK_ALIGNMENT | 기본 4, 1·2·4·8 | 행 패딩이 출력 배열 크기에 영향 |
| PACK_ROW_LENGTH·SKIP | 기본 0 | 기존 설정이 남으면 예상과 다른 주소에 기록 |
| PIXEL_PACK_BUFFER | 초기 0 | 비0이면 data는 버퍼 오프셋 |
위 예제는 row length·skip도 기본 상태인 경우입니다. 기존 렌더러에 통합할 때 pack 상태를 보존·복원하거나 호출 계약으로 고정합니다. 읽은 첫 행의 방향과 이미지 파일의 행 방향도 맞춥니다.
PBO 경로
// pbo에 충분한 저장소를 할당한 뒤, GL 3.3 Core에서 사용합니다.
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);nullptr는 CPU 포인터가 아니라 PBO 시작 오프셋 0입니다. 저장소가 부족하거나 일반 매핑 상태이면 전송이 잘못될 수 있습니다. 전송 후 fence를 넣고 나중 프레임에서 완료를 확인해 매핑·복사하면 제출과 회수를 분리할 수 있습니다. 결과 준비 전 PBO를 덮어쓰지 않습니다.
PBO는 PIXEL_UNPACK_BUFFER로 텍스처 업로드에도 사용할 수 있습니다. 이때는 glTexImage/SubImage의 data가 오프셋이 됩니다. 이름이 같은 버퍼 객체여도 pack은 GPU→CPU 회수 경로, unpack은 CPU 자료를 GL이 읽는 경로라는 상태 구분이 필요합니다.
전송과 나중 회수
GL 3.3에서 양수 width·height의 RGBA8 읽기를 예약합니다. 바이트 계산이 GLsizeiptr와 CPU 크기 범위를 넘지 않는지 먼저 확인합니다. 읽기 대상과 PACK_ROW_LENGTH·SKIP 값은 앞의 조건대로 준비합니다.
GLsizeiptr bytes = static_cast<GLsizeiptr>(width) * height * 4;
GLuint pbo = 0;
glGenBuffers(1, &pbo);
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
glBufferData(GL_PIXEL_PACK_BUFFER, bytes, nullptr, GL_STREAM_READ);
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
GLsync ready = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);ready가 null이면 생성 실패를 처리하며 미확인 데이터를 읽지 않습니다. 같은 컨텍스트의 나중 프레임에서 아래 구간을 수행합니다. <vector>·<cstring>을 포함하고, rgba는 회수 결과를 담을 빈 벡터입니다.
std::vector<unsigned char> rgba;
GLenum state = glClientWaitSync(ready, GL_SYNC_FLUSH_COMMANDS_BIT, 0);
if (state == GL_ALREADY_SIGNALED || state == GL_CONDITION_SATISFIED) {
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
const void* mapped = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, bytes, GL_MAP_READ_BIT);
if (mapped != nullptr) {
rgba.resize(static_cast<std::size_t>(bytes));
std::memcpy(rgba.data(), mapped, rgba.size());
if (glUnmapBuffer(GL_PIXEL_PACK_BUFFER) == GL_FALSE) rgba.clear();
}
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
glDeleteSync(ready);
ready = nullptr;
glDeleteBuffers(1, &pbo);
pbo = 0;
}TIMEOUT_EXPIRED면 ready와 pbo를 보관하고 나중에 다시 확인합니다. WAIT_FAILED면 오류를 처리하고 성공 분기로 들어가지 않습니다. 매핑 실패·unmap 실패에서는 이미지를 저장하지 않으며, 앱이 종료되면 남은 sync·PBO도 유효한 컨텍스트에서 정리합니다. 이 예제의 첫 예약 구간을 완료 전 같은 pbo에 반복 실행하면 아직 읽는 자료를 덮을 수 있습니다.
Resolve와 저장
완전한 사용자 read FBO의 GL_SAMPLE_BUFFERS가 0보다 크면 glReadPixels는 GL_INVALID_OPERATION입니다. 따라서 사용자 다중 샘플 FBO를 직접 읽는 대신 단일 샘플 대상으로 resolve한 뒤 읽는 경로를 사용합니다. 픽셀 읽기는 파일 인코딩이 아니므로 PNG·JPEG 저장은 별도 라이브러리의 책임입니다. 색 공간·알파·행 방향을 파일 형식과 함께 기록합니다.
버전과 동기화
직접 픽셀 읽기는 1.0부터이고 PBO는 2.1, sync fence는 3.2 코어 경로입니다. async라는 이름만으로 전송 비용이 0이 되지 않습니다. 메모리 가시성과 완료 대기에서 완료 신호와 버퍼 재사용을 연결합니다.
참고 링크
4 sources