Quick Comparison
GLSL 소스는 기본 컴파일 경로, 프로그램 바이너리는 구현별 시작 시간 단축용 캐시, SPIR-V는 OpenGL 대상의 사전 생성 셰이더 입력으로 선택합니다. 캐시의 구현 호환성과 SPIR-V의 대상 환경을 각각 확인합니다.
| 형태 | 읽는 단계 | 유지해야 할 조건 |
|---|---|---|
| GLSL 소스 | shader compile → program link | 언어 버전·매크로·인터페이스 |
| program binary | program에 직접 로드 | 구현 형식·드라이버·소스/설정 일치 |
| SPIR-V | shader binary → specialize → link | OpenGL용 환경·entry point·상수 |
프로그램 캐시
4.1 또는 ARB_get_program_binary 경로에서 지원 바이너리 형식 개수를 확인합니다. 캐시 사용을 의도하면 링크 전에 GL_PROGRAM_BINARY_RETRIEVABLE_HINT를 true로 요청하는 것이 권장됩니다. 필수 권한 스위치는 아니며 성공한 프로그램에서 길이·형식·데이터를 읽습니다. GL_NUM_PROGRAM_BINARY_FORMATS를 조회해 지원 형식 수가 0보다 큰지 먼저 확인합니다. 지원 형식 수가 0인 구현도 고려합니다.
// GL 4.1 Core: bytes·format은 호환되는 이전 glGetProgramBinary 결과입니다.
GLuint program = glCreateProgram();
glProgramBinary(program, format, bytes.data(), static_cast<GLsizei>(bytes.size()));
GLint linked = GL_FALSE;
glGetProgramiv(program, GL_LINK_STATUS, &linked);
if (!linked) {
glDeleteProgram(program);
program = 0;
// 캐시를 버리고 보관한 GLSL 소스로 새 프로그램을 만듭니다.
}캐시 키에는 소스·전처리 설정·링크 옵션과 구현 관련 식별 정보를 포함합니다. 같은 GPU에서도 드라이버가 바뀌면 로드가 실패할 수 있습니다. 성공한 로드 뒤 uniform의 위치·값을 필요한 초기 상태로 다시 준비합니다.
캐시 데이터 얻기
힌트의 초기값은 false입니다. 캐시를 사용할 프로그램은 링크 전에 true로 요청하고, 성공한 링크 뒤 반환 길이·형식과 함께 바이트를 저장합니다. 이 코드는 <vector>를 포함하며, program에 필요한 셰이더를 연결한 상태입니다.
glProgramParameteri(program, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE);
glLinkProgram(program);
GLint linked = GL_FALSE, length = 0;
glGetProgramiv(program, GL_LINK_STATUS, &linked);
std::vector<unsigned char> bytes;
GLenum format = 0;
if (linked) {
glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, &length);
if (length > 0) {
bytes.resize(length);
GLsizei written = 0;
glGetProgramBinary(program, length, &written, &format, bytes.data());
bytes.resize(written);
}
}빈 bytes는 사용할 캐시가 없다는 뜻으로 처리합니다. format은 파일 형식 이름으로 추정하지 않고 반환값을 저장합니다. 앞의 로드 예제는 이 bytes·format을 소비하며, 읽은 파일 길이가 GLsizei로 표현 가능한지도 확인합니다. 캐시 프로그램의 마지막 사용 뒤에는 일반 프로그램과 같이 삭제합니다.
SPIR-V 경로
OpenGL 4.6 또는 ARB_gl_spirv를 지원할 때 shader 객체에 SPIR-V를 넣고 entry point와 specialization constant를 정해 특수화합니다. 그 뒤 program 링크 상태를 확인합니다. program binary와 달리 프로그램 전체 캐시를 그대로 넣는 호출이 아닙니다.
바이너리는 OpenGL SPIR-V 환경의 허용 기능·메모리 모델·인터페이스 제약을 따라야 합니다. 컴파일 도구가 SPIR-V를 출력한다는 사실만으로 대상 환경이 맞는 것은 아닙니다. 생성 도구의 OpenGL 대상 옵션과 실제 요구 확장을 맞춥니다.
SPIR-V 입력과 특수화
GL 4.6 Core의 단계 생성 구간입니다. spirvWords는 OpenGL 대상으로 생성·검증된 std::vector<std::uint32_t>이고, main이라는 정점 entry point를 포함합니다. 빈 데이터·손상·길이 overflow를 파일 입력 단계에서 처리하고 호출합니다.
GLuint shader = glCreateShader(GL_VERTEX_SHADER);
if (shader != 0) {
const GLsizei byteLength = static_cast<GLsizei>(spirvWords.size() * sizeof(std::uint32_t));
glShaderBinary(1, &shader, GL_SHADER_BINARY_FORMAT_SPIR_V,
spirvWords.data(), byteLength);
glSpecializeShader(shader, "main", 0, nullptr, nullptr);
GLint ready = GL_FALSE;
glGetShaderiv(shader, GL_COMPILE_STATUS, &ready);
if (!ready) {
// glGetShaderInfoLog로 원인을 기록합니다.
glDeleteShader(shader);
shader = 0;
}
}이 경로에서는 glCompileShader 대신 glSpecializeShader를 사용합니다. 성공한 shader를 다른 준비된 SPIR-V 단계들과 program에 연결하고 링크 상태를 확인한 뒤, 기존 shader 삭제 책임을 동일하게 수행합니다. 한 프로그램 안에서 SPIR-V와 비-SPIR-V shader를 임의로 혼합하지 않습니다.
특수화할 정수 상수가 있으면 GLuint ids[] = {7}; GLuint values[] = {16};과 count 1을 넘겨 constant_id 7의 값을 16으로 정할 수 있습니다. ID는 uniform location이 아니며 entry point도 함수 포인터가 아닌 모듈 안 이름입니다. float 값은 GLuint로 수치 변환하지 않고 해당 비트 표현을 전달합니다. ARB 확장 경로에서는 glSpecializeShaderARB 등 실제 로드한 진입점을 사용합니다.
실패·선택 기준
개발 중에는 소스 경로가 오류 로그와 반복 수정에 편리합니다. 시작 시간을 줄이기 위한 프로그램 캐시는 실패 시 소스 재생성 경로를 남깁니다. SPIR-V는 셰이더 빌드 파이프라인이 명확할 때 선택하고, 최소 3.3 독자를 위해 필요한 GL 버전을 바로 표시합니다.
버퍼 길이·파일 손상·지원 형식·특수화 인수 오류를 구분합니다. 출처가 다른 바이너리를 로드한 뒤 GL_LINK_STATUS를 보지 않고 실행하지 않습니다. 일반 소스 경로는 컴파일과 링크에 연결됩니다.
참고 링크
4 sources