사전 스트림 출력
개념

스트림 출력

gabury1고친 사람 github-actions[bot]

스트림 출력은 그래픽 카드가 계산한 정점을 화면에 칠하기 전에 메모리에 적어 둡니다. 적어 둔 결과는 다음 그리기에서 입력으로 다시 쓸 수 있습니다. 한 번 한 계산을 버리지 않고 이어 쓰려고 둔 출구입니다.

쉽고 빠른 이해

정점은 도형을 이루는 점입니다. 스트림 출력은 그래픽 카드가 그림을 그리는 도중에 정점 계산 결과를 메모리로 빼 둡니다. 불티 만 개의 위치를 한 번 옮겨 새 위치를 적어 둡니다. 다음 화면은 그 위치에서 이어서 옮깁니다.

이게 없으면 그래픽 카드가 계산한 정점은 화면에 칠해지고 사라집니다. 같은 결과가 다시 필요하면 처음부터 다시 계산해야 합니다.

어떻게 도나:

  1. 어떤 값을 어느 버퍼에 적을지 미리 정해 둡니다
  2. 그리기를 돌리면 도형이 하나씩 버퍼에 차례로 쌓입니다
  3. 다음 그리기가 그 버퍼를 정점 입력으로 읽습니다

대가는 버퍼 크기를 미리 잡아야 한다는 것입니다. 모자라면 뒤에 오는 도형은 적히지 않고 버려집니다. 버퍼의 아무 칸에나 값을 써야 하는 계산이면 이 방법 대신 컴퓨트 셰이더를 씁니다.

상세

파이프라인과 두 갈래

정점은 도형을 이루는 점 하나입니다. 위치와 색 같은 값이 붙어 다닙니다. 삼각형 하나는 정점 셋으로 이루어집니다.

그래픽 카드(GPU, Graphics Processing Unit, 그래픽 처리 장치)는 정점을 받아 화면의 점으로 바꿀 때 정해진 순서로 단계를 밟습니다. 이 순서 전체가 그래픽스 파이프라인입니다. 줄여서 파이프라인이라고도 합니다.

파이프라인은 크게 둘로 나뉩니다. 앞쪽 단계는 정점을 다룹니다. 뒤쪽 단계는 화면의 점인 픽셀을 다룹니다. 맨 앞 단계는 메모리에서 정점을 읽어 오는 일을 합니다.

몇몇 단계에는 개발자가 짧은 프로그램을 꽂습니다. 그 프로그램이 셰이더입니다. 정점을 어디로 옮길지, 픽셀을 무슨 색으로 칠할지를 셰이더가 정합니다.

정점 셰이더는 앞쪽 단계에서 도는 셰이더입니다. 정점마다 새 위치를 정합니다. 캐릭터가 걸으면 다리의 정점을 옮기는 계산이 여기서 돕니다.

지오메트리 셰이더는 정점 셰이더 뒤에 붙일 수 있는 단계입니다. 도형 하나를 받아 여러 개로 불리거나 버립니다. 쓰지 않을 때는 비워 둡니다.

래스터화는 앞쪽과 뒤쪽을 가르는 단계입니다. 도형이 화면의 어느 픽셀을 덮는지 골라냅니다. 그 뒤의 단계들이 픽셀마다 색을 정해 화면에 칠합니다.

래스터화를 지나면 정점은 더 이상 정점이 아닙니다. 픽셀 몇 개로 흩어져 버립니다. 정점을 정점인 채로 건지려면 래스터화 앞에서 빼내야 합니다.

스트림 출력은 정점을 다루는 마지막 단계와 래스터화 사이에 붙습니다. 흐름은 여기서 둘로 갈립니다. 한 갈래는 래스터화로 가서 화면에 칠해집니다. 다른 갈래는 같은 정점을 메모리에 적습니다.

flowchart TD
    A["정점 읽기"] --> B["정점 셰이더"]
    B --> C["지오메트리 셰이더 · 비워 둘 수 있다"]
    C --> SO["스트림 출력"]
    C --> R["래스터화"]
    R --> P["픽셀을 칠하는 단계들 · 화면"]
    SO --> M[("버퍼 · 메모리")]
    M -.->|다음 그리기의 입력| A

그림의 점선이 이 단계를 두는 까닭입니다. 메모리에 적힌 정점은 다음 그리기 때 파이프라인 맨 앞 단계가 다시 읽어 갈 수 있습니다.

빵 공장 라인을 떠올려 봅시다. 반죽은 모양 잡기를 마치면 오븐으로 들어갑니다. 이때 모양 잡은 반죽을 한 판 더 떠서 냉동 창고에 넣어 둡니다. 다음 날 라인은 그 반죽을 재료로 받아 다시 손봅니다.

모양 잡기는 정점을 다루는 단계에, 오븐은 래스터화에, 냉동 창고는 버퍼에 맞춰 읽으면 됩니다.

스트림 출력은 Direct3D 에서 쓰는 이름입니다. Direct3D 와 OpenGL 은 프로그램이 GPU 에 그리기를 시키는 대표적인 두 API(Application Programming Interface, 응용 프로그램 인터페이스)입니다.

OpenGL 은 같은 기능을 트랜스폼 피드백이라고 부릅니다. 정점을 변환한 결과를 되먹인다는 뜻입니다. 이름만 다르고 하는 일은 같습니다.

스트림 출력이 받아 적는 단계

스트림 출력이 받는 것은 정점을 다루는 단계 중 맨 마지막 단계의 결과입니다. 지오메트리 셰이더를 썼으면 그 출력이 들어옵니다. 지오메트리 셰이더를 비워 두면 정점 셰이더의 출력이 들어옵니다.

정점 셰이더와 지오메트리 셰이더 사이에는 테셀레이션 단계가 더 낄 수 있습니다. 도형을 잘게 쪼개 정점을 늘리는 단계입니다. 이 단계를 켜고 지오메트리 셰이더를 비웠다면, 테셀레이션의 마지막 셰이더인 도메인 셰이더의 출력이 들어옵니다.

어느 쪽이든 스트림 출력은 값을 새로 계산하지 않습니다. 계산은 셰이더가 합니다. 스트림 출력은 앞 단계가 내놓은 값을 골라 적기만 합니다.

도형 단위의 기록

GPU 가 한 번에 그리는 가장 작은 도형 단위가 프리미티브입니다. 점 하나, 선분 하나, 삼각형 하나가 프리미티브입니다.

스트림 출력은 정점을 낱개로 적지 않고 프리미티브 단위로 적습니다. 삼각형 하나면 정점 셋이 한 묶음으로 적힙니다. 묶음이 다 안 들어가면 그 삼각형은 통째로 빠집니다.

적는 순서는 프리미티브가 들어온 순서를 따릅니다. GPU 는 수천 개를 나란히 계산합니다. 그래도 버퍼에 적힐 때는 줄을 섭니다. 덕분에 다음 그리기에서 읽을 때 도형 순서가 흐트러지지 않습니다.

정점을 아껴 쓰는 그리기 방식은 풀려서 적힙니다. 삼각형 스트립은 정점 넷으로 삼각형 둘을 만드는 방식입니다. 가운데 정점 둘을 두 삼각형이 나눠 씁니다.

이 방식으로 그린 도형이 버퍼에 적힐 때는 삼각형 둘이 따로따로 적힙니다. 정점 넷이 여섯이 됩니다. 이렇게 삼각형마다 정점 셋을 따로 적는 방식이 삼각형 리스트입니다.

무엇을 어디에 적을지 정하는 선언

버퍼는 GPU 메모리에 잡아 둔 연속된 칸입니다. 정점을 담아 두는 버퍼가 정점 버퍼입니다. 스트림 출력은 이 정점 버퍼에 결과를 적습니다.

셰이더가 내놓는 정점에는 값이 여럿 붙어 있습니다. 그중 무엇을 적을지, 어느 버퍼에 몇 번째 칸부터 적을지를 그리기 전에 정해 둬야 합니다. 이 약속이 스트림 출력 선언입니다.

불티 하나를 정점 하나로 다루는 경우를 예로 들겠습니다. 정점 하나에 아래 세 값을 적기로 선언했다고 합시다.

값 담는 것 크기
위치 실수 셋 (가로 · 세로 · 깊이) 12바이트
속도 실수 셋 12바이트
나이 실수 하나 · 태어난 뒤 지난 시간 4바이트

실수 하나가 4바이트이니 정점 하나는 28바이트를 차지합니다. 불티 만 개를 적으려면 버퍼를 280,000바이트 이상 잡아 둬야 합니다.

적을 버퍼는 하나일 수도 있고 여럿일 수도 있습니다. 위치는 한 버퍼에, 속도와 나이는 다른 버퍼에 나눠 적도록 선언할 수도 있습니다.

버퍼 넘침

버퍼 크기는 그리기 전에 정해집니다. 셰이더가 도형을 몇 개 내놓을지는 그리기가 끝나야 압니다. 지오메트리 셰이더는 도형 개수를 바꾸기 때문입니다.

개수를 모르고 크기를 정하니 버퍼가 모자랄 수 있습니다. 버퍼가 차면 그 뒤에 오는 도형은 적히지 않고 버려집니다. 오류가 나지 않고 조용히 빠집니다.

이런 일을 알아채도록 GPU 는 개수를 셉니다. 실제로 적은 도형 수와, 다 적으려면 필요했던 도형 수를 따로 셉니다. 두 수가 다르면 버퍼가 모자랐던 것입니다.

적어 둔 결과를 다시 그리기

적어 둔 버퍼는 다음 그리기에서 정점 버퍼로 들어갑니다. 파이프라인 맨 앞에서 정점을 읽어 오는 단계가 입력 어셈블러입니다. 입력 어셈블러는 이 버퍼를 여느 정점 목록처럼 읽어 갑니다.

그런데 그리기를 부르려면 정점이 몇 개인지를 넘겨야 합니다. 그 개수는 GPU 만 압니다. 그리기를 부르는 쪽은 CPU(Central Processing Unit, 중앙 처리 장치)입니다.

CPU 가 그 개수를 읽어 오려면 GPU 가 일을 끝낼 때까지 기다려야 합니다. 평소 CPU 는 그리기 명령을 미리 쌓아 둡니다. GPU 는 쌓인 명령을 뒤따라 처리합니다.

CPU 가 개수를 받으려고 멈추면 새 명령이 쌓이지 않습니다. 그러면 GPU 도 곧 할 일이 떨어집니다. 한쪽이 기다리는 동안 둘 다 쉬게 됩니다.

그래서 개수를 묻지 않고 그리는 호출이 따로 있습니다. GPU 가 센 개수를 자기가 읽어서 그만큼 그립니다. Direct3D 에서는 DrawAuto, OpenGL 에서는 glDrawTransformFeedback 이 이 호출입니다.

그리지 않고 계산만 받기

스트림 출력을 쓰면서 화면에는 아무것도 안 칠하게 할 수도 있습니다. 흐름이 둘로 갈리는 곳에서 래스터화 쪽 갈래를 끄는 것입니다.

이렇게 하면 GPU 가 계산기처럼 쓰입니다. 정점을 넣으면 셰이더가 계산하고 결과가 버퍼에 쌓입니다. 화면은 그대로 두고 값만 받습니다.

GPU 로 그림이 아닌 계산을 하는 일이 GPGPU(General-Purpose computing on GPU, 범용 GPU 계산)입니다. 스트림 출력은 파이프라인 안에서 이 일을 하는 방법 중 하나입니다.

한 화면씩 이어 가는 입자

연기나 불티처럼 작은 입자 수천 개를 그리는 기법이 파티클 시스템입니다. 입자마다 위치와 속도가 있습니다. 화면을 한 장 그릴 때마다 위치를 조금씩 옮깁니다. 화면 한 장이 프레임입니다.

입자 위치를 GPU 에서 옮기려면 지난 프레임의 위치가 필요합니다. 스트림 출력이 그 위치를 버퍼에 남겨 줍니다.

버퍼 둘을 번갈아 씁니다. 한 프레임은 버퍼 A 를 읽어 새 위치를 버퍼 B 에 적습니다. 다음 프레임은 B 를 읽어 A 에 적습니다. 읽는 버퍼와 적는 버퍼가 같으면 아직 안 읽은 값을 덮어쓰게 되기 때문입니다.

flowchart TD
    subgraph F1["프레임 1"]
        A1["버퍼 A · 지난 위치"] --> S1["셰이더 · 위치를 옮긴다"]
        S1 --> B1["버퍼 B · 새 위치"]
    end
    subgraph F2["프레임 2"]
        B2["버퍼 B · 지난 위치"] --> S2["셰이더 · 위치를 옮긴다"]
        S2 --> A2["버퍼 A · 새 위치"]
    end
    B1 -->|다음 프레임에 읽는다| B2

지오메트리 셰이더를 함께 쓰면 입자 수도 바뀝니다. 나이가 다 찬 입자는 내보내지 않아 없앱니다. 새 입자는 더 내보내 만듭니다. 입자가 태어나고 사라지는 일까지 GPU 안에서 끝납니다.

한 번 한 계산을 여러 번 쓰기

같은 장면을 한 프레임 안에 여러 번 그리는 경우가 있습니다. 그림자를 구하려고 한 번, 화면에 칠하려고 또 한 번 그리는 식입니다. 이 한 번 한 번이 렌더 패스입니다.

캐릭터는 뼈대가 움직이면 정점이 따라 움직입니다. 이 계산이 스키닝입니다. 정점이 많으면 계산도 무겁습니다.

스트림 출력으로 스키닝 결과를 한 번 적어 두면 뒤따르는 렌더 패스들은 그 버퍼를 읽기만 하면 됩니다. 패스마다 같은 계산을 되풀이하지 않습니다.

Vulkan 과 컴퓨트 셰이더

또 다른 그래픽 API 인 Vulkan 은 이 기능을 기본에 넣지 않았습니다. 확장으로만 둡니다.

같은 계산은 컴퓨트 셰이더로도 할 수 있습니다. 컴퓨트 셰이더는 그래픽스 파이프라인 밖에서 도는 프로그램입니다. 버퍼의 아무 칸이나 읽고 씁니다.

둘의 차이는 제약에 있습니다. 스트림 출력은 프리미티브 단위로, 들어온 순서대로만 적습니다. 컴퓨트 셰이더에는 그런 제약이 없습니다. 대신 순서가 필요하면 개발자가 직접 맞춰야 합니다.

도형을 들어온 순서대로 담기만 하면 되는 일에는 스트림 출력을 씁니다. 버퍼의 아무 칸에나 써야 하는 일에는 컴퓨트 셰이더를 씁니다.

관련 항목

스트림 출력 앞뒤에 놓이는 처리 단계

그래픽스 파이프라인 · 입력 어셈블러 · 정점 셰이더 · 테셀레이션 · 도메인 셰이더 · 지오메트리 셰이더 · 래스터화 · 픽셀 셰이더 · 출력 병합기

스트림 출력이 적어 넣는 버퍼와 그 구성

버퍼 · 정점 버퍼 · 비디오 메모리 · 스트라이드 · 정점 속성 · 시맨틱

스트림 출력이 도형 단위로 다루는 데이터

정점 · 프리미티브 · 삼각형 스트립 · 삼각형 리스트 · 인접 프리미티브

적어 둔 결과를 다시 그리는 방식

DrawAuto · 인다이렉트 드로 · 렌더 패스 · 핑퐁 버퍼 · 멀티 패스 렌더링

스트림 출력으로 푸는 계산

파티클 시스템 · 스키닝 · GPGPU · 물리 시뮬레이션 · 프레임

스트림 출력을 대신할 수 있는 다른 수단

트랜스폼 피드백 · 컴퓨트 셰이더 · 메시 셰이더 · UAV

스트림 출력을 정의한 그래픽 API

Direct3D · Direct3D 11 · Direct3D 12 · OpenGL · Vulkan

스트림 출력을 돌리는 장치와 프로그램

GPU · CPU · 셰이더 · HLSL · GLSL

다른 이름: stream output · stream-output · SO · 스트림 아웃풋