05

Part II · Data Flow Chapter 05 of 18 Build 2025.31550

POP → TOP / POP → CHOP

“GPU 위 데이터의 도메인 변환은 비용 없는 재해석이 아니다. Render TOP은 단지 컴파일된 draw call 묶음이다.”

이 핸드북의 핵심 명제 — "POP은 GPU 그래픽스 파이프라인의 데이터 모델을 노드 그래프로 노출시킨 학습 환경" — 가 이 챕터에서는 "도메인 변환은 공짜가 아니다"로 구체화된다. 4장에서 본 "texture = buffer" 등식은 메모리 차원의 진술이었다. 그러나 같은 메모리를 다른 도메인으로 "보여주는" 행위에는 비용이 따른다. 특히 POP을 화면 픽셀로 만드는 일 — 우리가 "렌더링"이라 부르는 그것 — 은 정점 변환과 래스터화라는 명시적 계산을 수반한다. Render TOP은 마술이 아니라 compile된 draw call의 묶음이다.

이 챕터에서 정복할 CG 개념

왜 이게 중요한가

학습자는 종종 노드 그래프의 시각적 균질함에 속는다. TOP을 POP에 꽂는 것과 POP을 TOP에 꽂는 것이 한 줄로 그려지면, 두 작업의 비용이 같다고 착각하기 쉽다. 그러나 4장의 TOP→POP은 메모리 reinterpret에 가까운 작업이고, 이 챕터의 POP→TOP은 다르다. POP의 정점이 픽셀로 가려면 vertex shader가 모든 점에 대해 MVP 변환을 수행하고, primitive assembly가 삼각형을 만들고, rasterizer가 픽셀 단위로 fragment를 만들고, fragment shader가 각 fragment의 색을 계산해야 한다. 이것이 raster pipeline 그 자체다. Render TOP은 그 파이프라인을 한 번 cook할 때마다 풀어내는 wrapper다. 같은 사실을 LearnOpenGL의 "Hello Triangle"이 가장 짧은 코드로 보여주고, RTR4 §2가 가장 정식으로 정리한다.

POP에서의 노출 지점

POP을 다른 도메인으로 보내는 경로는 wiki에서 다음과 같이 확인된다.

여기서 확인 가능한 노드만 정직하게 나열하면:

이론

POP의 attribute가 화면 픽셀이 되는 경로를 두 가지로 나눠 본다.

경로 (a): POP → Geometry COMP → Render TOP

이것은 raster pipeline의 정식 행로다. 매 cook마다 다음이 일어난다.

[POP attributes on GPU]
    P, N, Cd, Tex, primitive index buffer
            |
            v
   Vertex Shader (per point)
   - P를 World/View/Clip space로 변환 (MVP)
   - Cd, N, Tex 등을 varying으로 통과
            |
            v
   Primitive Assembly
   - index buffer로 triangle/line/point primitive 조립
            |
            v
   Rasterization
   - 각 primitive를 픽셀로 분할
   - varying을 픽셀별로 interpolate
            |
            v
   Fragment Shader (per pixel)
   - 보간된 N, Tex 등으로 색 계산
            |
            v
   Framebuffer (TOP pixels)

비용은 두 곳에서 발생한다. Vertex stage는 POP의 포인트 수에 비례. Fragment stage는 화면에 차지하는 픽셀 수(= 커버리지)에 비례. POP을 100만 포인트로 키운다고 fragment 비용이 100만 배 되지 않는다 — 화면 커버리지가 그대로면 fragment 비용은 그대로다. 반대로 화면을 4K로 키우면 fragment 비용은 4배가 되지만 vertex 비용은 동일하다. 이 비대칭은 raster pipeline의 핵심 특성이다.

경로 (b): POP attribute → TOP 픽셀로 직접 (별도 메커니즘)

이 경로는 rasterization을 건너뛴다. 즉 vertex shader도 fragment shader도 호출되지 않는다. 대신 POP의 N개 element를 TOP의 N개 픽셀에 1:1 매핑한다. attribute가 RGBA channel로 그대로 들어간다. 이것은 "POP의 메모리를 다른 도메인으로 보이게만 한다"는 점에서 4장의 TOP→POP과 대칭이다.

여기서 중요한 것: 이 두 번째 경로의 정확한 wiki-confirmed 노드명은 인벤토리에 등재되어 있지 않다. 인벤토리 §"Notes" 4번이 명시하듯 "rendering" 자체는 Geometry COMP → Render TOP 한 경로만 documented되어 있다. POP-as-data-source를 TOP으로 직접 보내는 별도 노드의 존재 여부는 wiki 미확정 — 실험 필요.

Render TOP은 무엇인가

LearnOpenGL "Hello Triangle"의 마지막 코드를 보면, 매 프레임 호출되는 라인은 본질적으로 두 줄이다 — glUseProgram(...)glDrawElements(...). Render TOP의 한 번의 cook은 그 두 줄을 자기 입력에 맞춰 컴파일하고 실행한다. 다음을 포함한다:

Render TOP은 마술적인 "POP을 픽셀로"가 아니다. 컴파일된 draw call 묶음이다. RTR4 §2가 이 사실을 정식으로 정리한다.

POP→CHOP의 의미

CHOP는 시간 축 또는 1D 인덱스에 따른 채널이다. POP의 attribute를 CHOP 채널로 보내는 행위는 본질적으로 GPU의 SSBO를 channel buffer로 reinterpret하는 것이다. 그러나 실제 구현은 두 가지로 갈린다.

정확한 통로 노드명과 readback 동기화 정책은 wiki 미확정 — 실험 필요. 인벤토리에서 확인된 후보 시작점은 Analyze POP 한 점 출력 → 후속 CHOP 추출 통로다.

손작업 (Hands-on)

시작 파일 A — 정식 렌더링 경로

연결: Sphere POP1 → Normal POP1 → (Geometry COMP1 내부). Render TOP1과 Render Simple TOP1은 같은 Geometry COMP를 입력으로 가진다.

1단계: 두 노드의 결과 비교

2단계: POP을 키워 vertex 비용을 본다

3단계: 화면 크기를 키워 fragment 비용을 본다

시작 파일 B — Trail POP으로 시간 축 누적

1단계: 시간이 누적된다

노드 ↔︎ GLSL 매핑

이 챕터의 주제는 "데이터 이동"이라 GLSL 비중은 작다. 한 가지 짧은 예 — Analyze POP의 sum-style reduction을 GLSL POP 한 번의 dispatch로 흉내내는 idea code — 만 보인다. atomic add 패턴이며, Read-Write 출력 모드가 필수다.

노드 구성

Compute Shader (idea code)

// GLSL POP, Attribute Class = Point, Output Attributes = P (and SumOut as new attribute)
// Output Access = readwrite (atomic을 쓰려면 필수)
// Pass 1에서 SumOut[0]을 0으로 초기화한다고 가정.

layout(std430, binding = 0) buffer SumBuf {
    float gSum[]; // SumOut과 매핑되는 SSBO. wiki는 'attribType AttribName[]' 형식을 명시한다.
};

void main() {
    const uint id = TDIndex();
    if (id >= TDNumElements()) return;

    vec3 p = TDIn_P();

    // sphere 위 한 점의 z 좌표를 모든 점에 대해 누적.
    // atomicAdd는 float이 아닌 uint/int에만 정의되므로,
    // 정밀도 손실을 감수하고 fixed-point로 변환하거나
    // float atomic 확장을 활성화해야 한다 (벤더/드라이버 의존).
    // 여기서는 개념 시연 — 실제 코드는 uint atomic + scale로 작성.
    uint scaled = uint(clamp(p.z * 1024.0 + 1024.0, 0.0, 4095.0));
    atomicAdd(gSumU[0], scaled); // gSumU는 uint 버전 SumOut.

    // P는 그대로 통과시킨다.
    P[id] = p;
}

이 코드가 GPU에서 실제로 무엇을 하고 있는가: 만 개의 thread가 동시에 같은 uint 메모리 슬롯에 add를 시도한다. atomic 연산이 그 add를 직렬화한다(드라이버/하드웨어가 lock-free로 직렬 효과를 보장한다). 결과적으로 한 번의 dispatch로 만 개 점의 z 좌표 합이 한 슬롯에 모인다. Analyze POP의 sum 모드가 내부에서 하는 일과 같은 카테고리의 작업이다. 현실적 주의: float atomic은 GLSL 표준이 아니라 확장이므로(NV_shader_atomic_float 등), 표준 GLSL POP에서 동작 보장이 안 된다. uint atomic + scale이 안전한 패턴이며, 정밀도가 중요하면 prefix sum(GPU Gems 3 Ch.39) 같은 reduction 알고리즘으로 다단 작성한다.

wiki 미확정 — 실험 필요: GLSL POP에서 출력 SSBO의 정확한 binding qualifier와 layout이 사용자에게 노출되는 방식(자동 주입 vs 직접 선언)은 wiki에 verbatim 확정 형태로 등장하지 않는다. 위 코드는 일반 compute shader 패턴을 따른 idea code다. 실제 코드는 wiki의 attribType AttribName[] 패턴(verbatim)으로 시작해 atomic 연산을 추가하는 형태가 될 것이다.

확인 질문 (Self-check)

  1. 같은 sphere를 Render TOP 두 개에 동시에 꽂아 다른 Resolution(512×512와 2048×2048)으로 받으면, vertex 비용과 fragment 비용은 각각 어떻게 변하는가? 왜 비대칭이 발생하는가?
  2. Render Simple TOP의 내부 자동 구성에서 라이트가 없는 상태로 sphere를 그리면 어떤 결과가 나오는가? 그 결과는 Render TOP에서 라이트를 0개로 두고 그린 결과와 같은가?
  3. Trail POP은 N프레임을 누적한다. 같은 결과를 Feedback POP과 Cache POP의 조합으로 구현하려면 어떻게 해야 하는가? 세 노드의 책임 분담은 무엇이 다른가?
  4. POP attribute를 CHOP 채널로 끌어내는 정식 노드명을 wiki에서 찾을 때 어떤 검색어가 유효한가? 인벤토리의 어떤 카테고리를 우선 살피겠는가?
  5. atomic add 기반의 reduction과 prefix-sum 기반의 reduction은 같은 결과를 만든다. 어느 쪽이 throughput에 유리한가? 어느 쪽이 numerical 정확도에 유리한가?

연결 고리

이 챕터의 한 줄 명제

Render TOP은 마술이 아니라 컴파일된 draw call 묶음이다. POP을 다른 도메인(픽셀, CHOP 채널, 다른 POP)으로 보내는 행위는 메모리 reinterpret(거의 공짜)일 수도 있고 raster pipeline 전체 실행(정점/픽셀당 계산 비용)일 수도 있으며, 두 가지를 구분하는 것이 GPU 위 데이터 흐름을 읽는 첫 단계다.