Quick Flow
캡처할 varying 지정 → 프로그램 링크
→ 충분한 출력 버퍼를 TF binding point에 연결
→ 필요하면 RASTERIZER_DISCARD
→ BeginTransformFeedback → draw → EndTransformFeedback
→ 입력·출력 버퍼 역할 교체Transform feedback은 프래그먼트 색이 아니라 정점 처리 단계의 출력을 저장합니다. 다음 프레임의 입력으로 재사용하려면 출력 형식과 버퍼 범위를 명확히 정합니다.
링크 이전의 캡처 선언
// GL 3.3 Core, program에 컴파일된 셰이더가 연결되어 있습니다.
const char* outputs[] = {"nextPosition", "nextVelocity"};
glTransformFeedbackVaryings(program, 2, outputs, GL_INTERLEAVED_ATTRIBS);
glLinkProgram(program);
// LINK_STATUS·로그를 확인한 뒤 실행합니다.varying 이름은 마지막 활성 정점 처리 단계의 출력과 맞아야 합니다. geometry shader가 있으면 그 출력이 대상입니다. 링크 후 이름만 다시 지정했다고 기존 실행 프로그램의 캡처 구조가 바뀌지 않습니다.
실행과 레이아웃
| 값 | 선택 | 의미·실패 |
|---|---|---|
| INTERLEAVED_ATTRIBS | 한 버퍼에 출력을 순서대로 기록 | 성분 순서·stride를 소비 VAO와 맞춤 |
| SEPARATE_ATTRIBS | 출력별 버퍼 분리 | 별도 binding·한도 확인 |
| Begin mode | POINTS·LINES·TRIANGLES | 실제 출력 프리미티브와 호환 |
| RASTERIZER_DISCARD | 초기 비활성 | 켜면 래스터화 없이 캡처, 다음 패스 전 해제 |
| 출력 범위 | 충분한 바이트 크기 | 증폭 단계의 최대 출력량 포함 |
glUseProgram(program);
glBindVertexArray(inputVao);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, outputBuffer);
glEnable(GL_RASTERIZER_DISCARD);
glBeginTransformFeedback(GL_POINTS);
glDrawArrays(GL_POINTS, 0, particleCount);
glEndTransformFeedback();
glDisable(GL_RASTERIZER_DISCARD);프로그램과 입력 VAO가 준비되어 있고 point를 출력하는 예입니다. 같은 저장소를 입력과 TF 출력으로 겹치게 사용하는 기본 구조를 피하고, 두 버퍼를 번갈아 쓰면 수명과 데이터 흐름을 추적하기 쉽습니다.
저장되는 두 vec3의 형태
앞서 캡처 이름으로 지정한 두 출력은 다음과 같이 실제 셰이더에서 정의할 수 있습니다. GL 3.3에서 점 하나당 위치와 속도를 갱신하며, dt는 CPU가 uniform으로 전달하는 초 단위 시간입니다.
#version 330 core
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 velocity;
uniform float dt;
out vec3 nextPosition;
out vec3 nextVelocity;
void main() {
nextPosition = position + velocity * dt;
nextVelocity = velocity;
gl_Position = vec4(nextPosition, 1.0);
}INTERLEAVED_ATTRIBS로 두 vec3를 이 순서대로 캡처하면 점 하나는 float 여섯 개, 24바이트입니다. 이것은 std140 UBO의 vec3 정렬과 다른 저장 규칙입니다. 출력 버퍼는 particleCount * 6 * sizeof(GLfloat) 이상이어야 합니다. 다음 입력 VAO는 위치 offset 0, 속도 offset 3 * sizeof(GLfloat), 공통 stride 6 * sizeof(GLfloat)로 연결합니다.
링크가 성공한 뒤 실제 캡처 이름·배열 크기·타입을 확인할 수도 있습니다. <vector>와 <cstdio>가 필요한 조회 구간입니다.
GLint varyingCount = 0, maxName = 0;
glGetProgramiv(program, GL_TRANSFORM_FEEDBACK_VARYINGS, &varyingCount);
glGetProgramiv(program, GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH, &maxName);
std::vector<GLchar> name(maxName > 0 ? maxName : 1);
for (GLuint i = 0; i < static_cast<GLuint>(varyingCount); ++i) {
GLsizei written = 0;
GLsizei arraySize = 0;
GLenum type = 0;
glGetTransformFeedbackVarying(program, i, static_cast<GLsizei>(name.size()),
&written, &arraySize, &type, name.data());
std::printf("%s: size=%d type=0x%x\n", name.data(), arraySize, type);
}이름이 실제 마지막 단계의 출력에 없거나, interleaved 총 성분 한도·separate 성분/출력 개수 한도를 넘으면 링크가 실패할 수 있습니다. 각각 GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS, GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS, GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS를 확인합니다. varying 조회는 실패한 링크를 유효하게 만드는 호출이 아닙니다.
결과 재사용
캡처된 layout에 맞는 정점 속성 설정을 준비합니다. CPU의 구조체 패딩을 TF 출력 규약이라고 가정하지 않습니다. 활성 capture 중 프로그램·버퍼를 임의로 바꾸는 제약도 확인합니다. 모든 출력이 항상 입력 정점 수와 같지는 않으므로 geometry 증폭·프리미티브 변화가 있으면 저장 용량을 따로 계산합니다.
버전과 Compute 비교
기본 transform feedback은 3.0, TF 객체·관련 draw와 pause/resume 경로는 이후 4.0 기능을 구분합니다. 3.3 예제에서 glDrawTransformFeedback을 기본 호출처럼 넣지 않습니다. 일반 메모리 접근과 작업 그룹이 필요한 계산은 4.3 Compute를 비교하되, TF가 더 높은 버전의 기능 때문에 반드시 폐기되는 것은 아닙니다.
참고 링크
5 sources