사전 Direct3D 11
표준

Direct3D 11

gabury1고친 사람 github-actions[bot]

Direct3D 11 은 Windows 프로그램의 그리기 명령을 그래픽 카드에 전합니다. Microsoft 가 정하는 그래픽 규격 Direct3D 의 열한 번째 세대입니다. 메모리 관리처럼 번거로운 일은 드라이버가 뒤에서 맡습니다. 그래서 코드가 짧습니다. 뒤이은 12 세대가 나온 뒤에도 나란히 쓰입니다.

쉽고 빠른 이해

Direct3D 11 은 Windows 프로그램이 그래픽 카드에 일을 시킬 때 부르는 함수 묶음입니다. 게임은 장면 한 장을 그릴 때 점 좌표를 올립니다. 칠할 작은 프로그램을 고른 뒤 그리라고 부릅니다. 이 일이 전부 이 묶음 안에서 일어납니다.

그래픽 카드는 회사마다 알아듣는 명령이 다릅니다. 이 묶음이 사이에 서면 프로그램은 카드 회사를 몰라도 됩니다. 메모리를 언제 잡고 풀지도 드라이버가 정합니다.

어떻게 도나:

  1. 점 좌표와 그림 파일을 그래픽 카드 메모리에 올립니다
  2. 점을 옮기고 픽셀을 칠할 작은 프로그램을 고릅니다
  3. 그리라고 부르면 드라이버가 명령을 모아 그래픽 카드로 보냅니다

대가도 있습니다. 드라이버가 뒤를 맡는 만큼 CPU(Central Processing Unit, 중앙 처리 장치) 시간이 더 듭니다. 여러 스레드가 명령을 나눠 준비해도 그래픽 카드로 보내는 곳은 한 군데뿐입니다. 그래서 보내는 일은 한 스레드에 몰립니다. Windows 와 Xbox 밖에서는 돌지 않습니다.

상세

Direct3D 11 은 Direct3D 의 열한 번째 세대입니다. Direct3D 는 Microsoft 가 정하는 3차원 그래픽 API(Application Programming Interface, 응용 프로그램 인터페이스)입니다. API 는 프로그램이 다른 소프트웨어에 일을 시킬 때 부르는 함수 목록입니다.

프로그램은 이 함수들을 불러 GPU(Graphics Processing Unit, 그래픽 처리 장치)에 일을 시킵니다. GPU 는 그래픽 카드에 박힌 칩입니다. 같은 계산을 많은 데이터에 한꺼번에 돌리는 데 강합니다.

함수 호출을 그 회사 GPU 가 알아듣는 명령으로 바꾸는 일은 드라이버가 맡습니다. 드라이버는 그래픽 카드 회사가 만들어 배포하는 소프트웨어입니다. 프로그램은 드라이버를 직접 부르지 않고 Direct3D 11 의 함수만 부릅니다.

그래픽 카드는 회사마다 알아듣는 명령이 다릅니다. 이 계층이 없으면 프로그램이 회사별로 다른 명령을 따로 짜야 합니다. 함수 목록 하나를 사이에 두면 그 차이는 드라이버가 떠안습니다.

Direct3D 11 은 Windows 7 과 함께 나왔습니다. 뒤로 11.1 부터 11.4 까지 작은 판이 이어졌습니다. 판마다 기능을 조금씩 더했습니다. 부르는 방식의 뼈대는 같습니다.

드라이버가 뒤를 맡는 세대

11 세대를 가르는 특징은 드라이버가 맡는 일의 양입니다. 뒤이은 Direct3D 12 와 나란히 놓으면 잘 보입니다.

11 세대에서 프로그램은 무엇을 그릴지만 지시합니다. GPU 메모리를 언제 잡고 풀지는 드라이버가 정합니다. 쌓인 명령을 언제 GPU 로 보낼지도 드라이버가 정합니다.

GPU 가 아직 읽고 있는 메모리를 프로그램이 덮어쓰면 화면이 깨집니다. 11 세대에서는 이런 충돌을 막는 동기화도 드라이버가 맡습니다. 12 세대는 이 일들을 프로그램으로 넘겼습니다.

아래 표는 같은 일을 두 세대에서 누가 맡는지 보입니다.

일 11 세대 12 세대
GPU 메모리를 잡고 풀기 드라이버 프로그램
명령을 GPU 로 보내는 시점 드라이버 프로그램
메모리 덮어쓰기 충돌 막기 드라이버 프로그램
코드 양 적다 많다

그래서 11 세대는 코드를 적게 쓰고 싶은 프로그램이 고릅니다. 대가는 CPU(Central Processing Unit, 중앙 처리 장치) 시간입니다. 드라이버는 프로그램의 사정을 모릅니다. 짐작으로 처리하는 만큼 CPU 가 더 일합니다.

그리기 호출이 많을수록 이 대가가 커집니다. 그리기 호출은 GPU 에 무언가를 그리라고 부르는 함수 한 번입니다. 한 장면에 그리기 호출이 아주 많은 게임은 12 세대로 옮겨 이 비용을 줄이기도 합니다.

Direct3D 11 은 Windows 와 Xbox 에서만 돕니다. 여러 운영체제에 같은 그리기 코드를 올리려면 Vulkan 이나 OpenGL 같은 다른 그래픽스 API 를 고릅니다. 게임 엔진처럼 여러 API 를 한 겹 감싸는 소프트웨어를 두기도 합니다.

11 세대의 그래픽스 파이프라인

그리기 호출 하나는 정해진 단계를 차례로 지나 픽셀 색이 됩니다. 이렇게 단계가 이어진 줄기를 그래픽스 파이프라인이라고 부릅니다. 11 세대는 10 세대의 단계를 모두 그대로 둡니다.

11 세대가 파이프라인에 더한 것은 테셀레이션입니다. 테셀레이션은 거친 도형 하나를 작은 삼각형 여럿으로 쪼개는 일입니다. 쪼갠 삼각형은 매끈한 곡면에 더 가깝습니다. 11 세대는 이 일을 맡는 단계 셋을 새로 넣었습니다.

단계 이름에는 정점과 셰이더라는 낱말이 자주 나옵니다. 정점은 좌표와 색 같은 값을 가진 점 하나입니다. 삼각형 하나는 정점 셋을 이어 만듭니다.

셰이더는 GPU 에서 도는 작은 프로그램입니다. 정점을 옮기거나 픽셀 색을 정하는 계산을 프로그래머가 셰이더로 써 넣습니다. 그래서 같은 파이프라인으로도 물체마다 다른 모습을 그릴 수 있습니다.

그림은 새 단계 셋이 파이프라인의 어디에 끼는지 보입니다. 앞뒤 단계는 둘씩 한 상자로 묶었습니다.

flowchart TD
    A["입력 어셈블러 · 정점 셰이더"] --> T
    subgraph T["11 세대가 더한 테셀레이션 세 단계"]
        HS["헐 셰이더 · 얼마나 쪼갤지 정한다"] --> TS["테셀레이터 · 도형을 쪼갠다"]
        TS --> DS["도메인 셰이더 · 새 점의 위치를 정한다"]
    end
    T --> G["지오메트리 셰이더"]
    G --> R["래스터라이저"]
    R --> P["픽셀 셰이더 · 출력 병합기"]

단계마다 하는 일은 아래 표와 같습니다. 이름에 셰이더가 붙은 단계에는 프로그래머가 셰이더를 써 넣습니다. 나머지 단계는 설정만 바꿉니다.

단계 하는 일 셰이더를 써 넣나
입력 어셈블러 메모리의 점을 읽어 삼각형 같은 도형으로 묶는다 ✗
정점 셰이더 점 하나하나를 화면 기준 위치로 옮긴다 ✓
헐 셰이더 도형을 얼마나 잘게 쪼갤지 정한다 ✓
테셀레이터 정해진 만큼 도형을 작은 삼각형으로 쪼갠다 ✗
도메인 셰이더 쪼개서 생긴 점의 위치를 정한다 ✓
지오메트리 셰이더 도형을 보고 더 만들거나 버린다 ✓
래스터라이저 도형이 화면의 어느 픽셀을 덮는지 찾는다 ✗
픽셀 셰이더 픽셀마다 색을 정한다 ✓
출력 병합기 가려진 픽셀을 버리고 색을 섞어 쓴다 ✗

래스터라이저가 하는 일은 래스터화라고 부릅니다. 테셀레이션 단계와 지오메트리 셰이더는 안 쓰면 건너뜁니다.

그림과 표에 없는 단계가 하나 더 있습니다. 스트림 출력은 지오메트리 셰이더 뒤에서 갈라지는 단계입니다. 도형을 화면 대신 메모리로 빼냅니다.

테셀레이션을 쓰면 가까운 물체는 촘촘하게, 먼 물체는 성기게 쪼갤 수 있습니다. 모델 파일에는 거친 도형만 담아 두면 됩니다. 세밀한 모양은 GPU 가 그릴 때 만들어 냅니다. 그래서 메모리에서 읽어 오는 데이터 양이 줄어듭니다.

셰이더는 HLSL(High Level Shader Language, 고수준 셰이더 언어)로 씁니다. C 와 닮은 문법의 언어입니다. 11 세대가 받는 HLSL 의 판을 셰이더 모델 5 라고 부릅니다.

디바이스와 디바이스 컨텍스트

Direct3D 11 의 함수는 두 객체에 나뉘어 있습니다. 무엇을 만드는 객체와 무엇을 그리는 객체입니다. 둘이 어떻게 갈리는지는 짧은 코드로 봅니다.

디바이스는 GPU 쪽 리소스를 만듭니다. 리소스는 GPU 메모리에 올려 두는 데이터입니다. 점 좌표를 담는 정점 버퍼, 그림 파일을 담는 텍스처가 그 예입니다.

디바이스 컨텍스트는 그 리소스로 그리기를 지시합니다. 어느 버퍼를 읽을지, 어느 셰이더를 쓸지 고른 뒤 그리라고 부릅니다. 파이프라인 단계의 설정도 컨텍스트에 겁니다.

아래는 삼각형 하나를 그리는 흐름에서 뼈대만 추린 C++ 코드입니다. dev 는 디바이스, ctx 는 디바이스 컨텍스트입니다.

C++
dev->CreateBuffer(&d, &i, &vb); // vb 생성
ctx->IASetVertexBuffers(0, 1, &vb, &st, &off);
ctx->VSSetShader(vs, nullptr, 0);
ctx->PSSetShader(ps, nullptr, 0);
ctx->Draw(3, 0);                // 삼각형

첫 줄만 디바이스를 부릅니다. 나머지는 전부 컨텍스트를 부릅니다. vs·ps 도 디바이스가 미리 만들어 둔 셰이더입니다.

함수 이름 앞의 IA·VS·PS 는 단계 이름의 머리글자입니다. 차례로 입력 어셈블러(Input Assembler), 정점 셰이더(Vertex Shader), 픽셀 셰이더(Pixel Shader)입니다. 마지막 줄은 정점 셋을 읽어 삼각형 하나를 그리라는 호출입니다.

인자 이름은 뼈대만 보이려고 줄였습니다. 각 인자의 뜻은 아래와 같습니다.

인자 뜻
d 만들 버퍼의 크기와 쓰임새를 적은 설명
i 버퍼에 처음 채울 정점 데이터
0, 1 0 번 슬롯부터 버퍼 하나를 건다
st 정점 하나가 차지하는 바이트 수
off 버퍼에서 읽기 시작할 위치
nullptr, 0 이 예에서 안 쓰는 추가 인자
3, 0 정점 셋을 첫 정점부터 읽는다

둘을 가른 까닭은 멀티스레딩입니다. 디바이스의 함수는 여러 스레드가 동시에 불러도 안전합니다. 한 스레드가 그림 파일을 읽어 텍스처를 만드는 동안 다른 스레드는 그리기를 이어 갈 수 있습니다.

컨텍스트는 다릅니다. 컨텍스트 하나는 한 번에 한 스레드만 써야 합니다.

즉시 컨텍스트와 지연 컨텍스트

디바이스 컨텍스트에는 두 종류가 있습니다. 그리기 준비를 CPU 코어 여럿에 나누려고 둔 구분입니다.

즉시 컨텍스트는 부르는 대로 명령을 드라이버에 넘깁니다. 디바이스마다 하나 있습니다. GPU 에서 결과를 읽어 오는 일도 이 컨텍스트만 합니다.

지연 컨텍스트는 명령을 바로 보내지 않고 커맨드 리스트에 적어 둡니다. 커맨드 리스트는 그리기 명령을 순서대로 녹화해 둔 목록입니다. 작업 스레드마다 지연 컨텍스트를 하나씩 두고 장면의 한 부분씩 녹화하게 합니다.

녹화가 끝난 목록은 즉시 컨텍스트가 받아 재생합니다. 즉시 컨텍스트는 보통 메인 스레드 하나가 쥡니다. 준비는 여러 코어가 나눠 해도 GPU 로 보내는 창구는 하나입니다.

flowchart TD
    subgraph W["작업 스레드"]
        D1["지연 컨텍스트 1"] --> L1["커맨드 리스트 1"]
        D2["지연 컨텍스트 2"] --> L2["커맨드 리스트 2"]
    end
    L1 --> I["즉시 컨텍스트 · 메인 스레드"]
    L2 --> I
    I --> DR["드라이버"]
    DR --> G["GPU"]

스레드가 하나뿐인 프로그램에는 지연 컨텍스트가 필요 없습니다. 새로 만든 지연 컨텍스트는 즉시 컨텍스트의 설정을 물려받지 않습니다. 쓸 셰이더와 버퍼를 처음부터 다시 걸어야 합니다.

컴퓨트 셰이더

11 세대는 그리기와 상관없는 계산도 GPU 에 맡기게 했습니다. 이 계산을 맡는 셰이더가 컴퓨트 셰이더입니다.

컴퓨트 셰이더는 GPU 를 범용 병렬 처리기로 쓰게 합니다. 수많은 값에 같은 계산을 한꺼번에 돌릴 때 씁니다. 화면 전체를 흐리게 만드는 후처리나 입자 수만 개의 물리 계산이 그런 예입니다. DirectCompute 라는 이름으로도 부릅니다.

컴퓨트 셰이더는 그래픽스 파이프라인에 끼지 않습니다. 대신 같은 디바이스로 만들기 때문에 그리기 셰이더와 리소스를 나눠 씁니다. 계산 결과를 CPU 로 옮기지 않고 곧장 그리기에 쓸 수 있습니다.

그리기 셰이더는 결과를 정해진 곳에만 씁니다. 픽셀 셰이더라면 자기가 맡은 픽셀 하나의 색입니다. 컴퓨트 셰이더는 결과를 메모리의 아무 칸에나 적을 수 있습니다.

그 쓰기를 여는 이름이 11 세대가 새로 들인 UAV(Unordered Access View, 순서 없는 접근 뷰)입니다. 뷰는 셰이더가 리소스에 닿는 통로입니다. 리소스는 그대로 두고 뷰만 따로 만들어 셰이더에 겁니다. UAV 는 아무 칸에나 읽고 쓰기를 허락하는 뷰입니다.

기능 레벨

11 세대 함수로 짠 프로그램이 옛 그래픽 카드에서도 돌게 하는 장치가 기능 레벨입니다. 아래에서는 레벨이 무엇을 묶는지와 프로그램이 레벨을 고르는 순서를 차례로 봅니다.

기능 레벨은 GPU 가 해내야 하는 기능을 묶어 이름 붙인 목록입니다. 9_1 은 9 세대 카드가 하던 기능을 뜻합니다. 11_0 은 11 세대 카드가 하던 기능을 뜻합니다. 높은 레벨은 낮은 레벨의 기능을 전부 품습니다.

기능 레벨 해내는 기능
9_1 · 9_2 · 9_3 9 세대 카드 수준
10_0 · 10_1 10 세대 카드 수준
11_0 11 세대 카드 수준. 테셀레이션을 포함한다
11_1 11.1 판에서 더해진 기능까지

프로그램은 디바이스를 만들 때 원하는 레벨을 요청합니다. 디바이스가 만들어지면 그 카드가 그 레벨을 해낸다는 뜻입니다. 실패하면 더 낮은 레벨로 다시 요청하거나 프로그램을 끝냅니다.

그래서 함수는 11 세대 것을 쓰면서 9 세대나 10 세대 카드에서도 돌 수 있습니다. 테셀레이션처럼 11 세대에서 생긴 기능은 낮은 레벨에서 못 씁니다. 레벨은 기능만 말하고 속도는 말하지 않습니다.

판과 레벨은 적는 법으로 가릅니다. 판은 마침표를 써서 11.1 로 적습니다. 기능 레벨은 밑줄을 써서 11_0 으로 적습니다. 11.1 판 위에서 기능 레벨 10_0 카드를 쓰는 조합도 됩니다.

관련 항목

Direct3D 11 이 속하는 상위 분류

Direct3D · DirectX · 그래픽스 API · API · 그래픽스 · 렌더링

Direct3D 11 과 같은 역할을 두고 겨루는 그래픽스 API

Direct3D 12 · OpenGL · Vulkan · Metal · WebGPU · WebGL

Direct3D 11 파이프라인이 거치는 처리 단계

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

Direct3D 11 이 GPU 메모리에 만드는 리소스

리소스 · 정점 버퍼 · 텍스처 · 상수 버퍼 · 렌더 타깃 · 스왑 체인 · UAV

Direct3D 11 로 명령을 모아 보내는 객체

디바이스 컨텍스트 · 즉시 컨텍스트 · 지연 컨텍스트 · 커맨드 리스트 · 디바이스 드라이버

Direct3D 11 이 여러 코어로 일을 나누는 기법

멀티스레딩 · 스레드 · 코어 · 동기화 · CPU

Direct3D 11 로 하는 GPU 범용 계산

컴퓨트 셰이더 · DirectCompute · GPGPU · CUDA · OpenCL · 병렬 처리

Direct3D 11 의 셰이더를 쓰는 언어와 규격

HLSL · 셰이더 모델 · 셰이더 · 기능 레벨 · 테셀레이션

Direct3D 11 을 만들고 돌리는 주체

Microsoft · Windows · Xbox · GPU · 게임 엔진

다른 이름: D3D11 · 다이렉트3D 11 · DirectX 11