그래픽 드라이버
고친 사람 github-actions[bot]
그래픽 드라이버는 프로그램이 보낸 그리기 요청을 그래픽 카드가 알아듣는 명령으로 바꿔 줍니다. 대개 그래픽 카드를 만든 회사가 자기 카드에 맞춰 만들어 내놓습니다. 덕분에 프로그램은 어느 회사 카드에서 돌지 몰라도 같은 방식으로 화면을 그립니다.
쉽고 빠른 이해
무슨 일을 하는 물건인가 — 프로그램의 「이 삼각형을 그려라」를 그래픽 카드가 읽는 명령으로 옮기는 소프트웨어입니다. 게임이 부른 그리기 함수는 이 소프트웨어를 거쳐야 그래픽 카드에 닿습니다.
왜 이렇게 하나 — 그래픽 카드는 회사마다, 세대마다 알아듣는 명령이 다릅니다. 드라이버가 없으면 프로그램이 모든 카드의 명령을 따로 배워야 합니다. 드라이버가 그 차이를 떠맡아서 프로그램은 공통 함수만 부르면 됩니다.
어떻게 도나
- 프로그램이 공통 그리기 함수를 부릅니다
- 드라이버가 그 호출을 카드의 명령으로 바꿔 한 묶음으로 모읍니다
- 묶음을 카드에 넘기면 카드가 그리고 끝났다고 알립니다
대가 — 프로그램이 드라이버의 품질에 묶입니다. 같은 코드도 드라이버에 따라 다르게 그려지거나 느려집니다. 드라이버가 멈추면 그 위의 화면도 같이 멈춥니다.
언제 만나나 — 그래픽 카드를 쓰는 프로그램은 게임이든 계산 서버든 모두 드라이버를 거칩니다. 그래픽 카드가 없는 기기에서는 소프트웨어 렌더러가 드라이버 노릇을 대신합니다. 그림 계산을 일반 프로세서가 떠맡는 방식이라 훨씬 느립니다.
상세
국제회의의 통역사를 떠올려 보세요. 연사는 자기 말로 한 번만 말합니다. 통역사가 그 말을 듣고 청중이 알아듣는 말로 옮깁니다. 청중이 바뀌면 연사는 그대로 두고 통역사만 바꿉니다.
그래픽 드라이버는 프로그램과 그래픽 카드 사이의 통역사입니다.
그래픽스 API 와 GPU 사이의 번역
그래픽 카드에서 그림을 계산하는 칩을 GPU(Graphics Processing Unit, 그래픽 처리 장치)라고 합니다. 노트북처럼 별도 카드 없이 CPU(Central Processing Unit, 중앙 처리 장치) 칩 안에 GPU 가 들어 있는 기기도 많습니다. 어느 쪽이든 드라이버가 상대하는 것은 이 칩입니다.
프로그램은 GPU 에 직접 명령하지 않습니다. 그래픽스 API(Application Programming Interface, 응용 프로그래밍 인터페이스)라는 공통 함수 목록을 부릅니다. OpenGL · Vulkan · Direct3D · Metal 이 그런 목록입니다. 「이 삼각형을 이 색으로 그려라」 같은 요청이 함수 호출 하나가 됩니다.
그래픽스 API 가 정하는 것은 함수의 이름과 뜻입니다. 그 함수를 어떤 GPU 명령으로 바꿀지는 정하지 않습니다. 그 번역을 GPU 마다 따로 만든 그래픽 드라이버가 맡습니다. 장치를 다루는 코드를 통틀어 디바이스 드라이버라고 합니다. 그래픽 드라이버는 그 가운데 GPU 를 맡은 것입니다.
GPU 가 알아듣는 명령의 형식은 제조사마다 다릅니다. 같은 회사라도 세대가 바뀌면 형식이 달라집니다. 형식을 공개하지 않는 제조사도 많아서 GPU 를 만든 회사가 드라이버도 함께 내놓는 것이 보통입니다. 드라이버가 이 차이를 떠맡지 않으면 프로그램이 GPU 마다 코드를 따로 써야 합니다.
사용자 공간 쪽과 커널 쪽
요즘 그래픽 드라이버는 두 부분으로 나뉩니다. 한 부분은 라이브러리로 프로그램 안에 올라와 돌아갑니다. 다른 부분은 운영체제의 중심부인 커널 안에서 돌아갑니다.
일반 프로그램이 도는 구역을 사용자 공간이라고 합니다. 사용자 공간에서 난 오류는 그 프로그램 하나만 죽입니다. 커널에서 난 오류는 기기 전체를 멈출 수 있습니다.
그래도 커널 쪽을 없앨 수는 없습니다. GPU 를 직접 만지는 일은 커널만 할 수 있습니다. 여러 프로그램이 한 GPU 를 같이 쓸 때 차례와 메모리를 나눠 주는 일도 커널이 맡습니다.
프로그램이 커널에 일을 부탁하는 호출을 시스템 콜이라고 합니다. 시스템 콜은 커널로 넘어갈 때마다 시간이 듭니다. 그런데 그리기 함수는 한 화면을 그리는 동안에도 수없이 불립니다. 호출마다 커널로 넘어가면 그 시간이 쌓입니다.
그래서 호출마다 하는 번역은 사용자 공간 쪽이 끝냅니다. 커널 쪽에는 다 만든 묶음만 한꺼번에 넘깁니다.
아래 그림은 그리기 요청 하나가 두 부분을 지나 GPU 에 닿는 길입니다.
flowchart TD
subgraph U["사용자 공간"]
A["프로그램"] --> B["그래픽스 API 호출"]
B --> C["사용자 공간 쪽 드라이버"]
end
subgraph K["커널"]
D["커널 쪽 드라이버"]
end
subgraph H["하드웨어"]
E["GPU"]
end
C --> D
D --> E
두 부분이 나눠 맡는 일은 아래와 같습니다.
| 사용자 공간 쪽 | 커널 쪽 | |
|---|---|---|
| 받는 것 | 그래픽스 API 호출 | 사용자 공간 쪽이 만든 명령 묶음 |
| 하는 일 | 셰이더 번역 · 명령 묶음 작성 | GPU 메모리 배분 · 묶음 제출 · 화면 출력 · 멈춘 GPU 재설정 |
| 오류가 나면 | 그 프로그램이 죽는다 | 기기 전체가 멈출 수 있다 |
셰이더를 GPU 의 기계어로 바꾸는 일
셰이더는 GPU 위에서 도는 작은 프로그램입니다. 점의 위치를 옮기거나 픽셀 하나의 색을 정하는 일을 셰이더에 맡깁니다. 개발자는 셰이더를 셰이더 전용 언어로 씁니다.
GPU 는 그 소스를 읽지 못합니다. 드라이버가 셰이더를 그 GPU 의 기계어로 번역합니다. 이 번역은 개발자가 빌드할 때가 아니라 사용자 기기에서 프로그램이 돌 때 일어납니다. 빌드할 때는 어느 GPU 에서 돌지 모르기 때문입니다.
번역을 두 단계로 나누기도 합니다. 빌드할 때 SPIR-V(Standard Portable Intermediate Representation, 표준 이식 중간 표현) 같은 중간 형식으로 바꿔 둡니다. 드라이버는 그 중간 형식을 받아 기계어로 마무리합니다.
번역에는 시간이 듭니다. 게임을 처음 켜거나 새 장면에 들어갈 때 화면이 잠깐 끊기는 까닭 가운데 하나가 이것입니다. 그래서 드라이버는 번역 결과를 디스크의 셰이더 캐시에 저장해 두었다가 다음번에 다시 씁니다.
명령을 모아 한꺼번에 넘기는 일
GPU 는 CPU 와 따로 돌아갑니다. CPU 가 명령을 하나 줄 때마다 GPU 가 끝내기를 기다리면 둘 다 자주 놉니다. 그래서 드라이버는 명령을 바로 넘기지 않고 커맨드 버퍼에 모아 둡니다. 커맨드 버퍼는 GPU 명령을 차곡차곡 적어 두는 메모리 구역입니다.
명령이 모이면 커널 쪽 드라이버가 커맨드 버퍼를 GPU 에 넘깁니다. 이 넘기는 일을 커맨드 서브미션이라고 합니다. GPU 는 받은 묶음을 자기 속도로 처리합니다. 그동안 CPU 는 다음 장면의 명령을 적습니다.
CPU 는 그린 결과를 쓰기 전에 GPU 가 그 묶음을 다 끝냈는지 알아야 합니다. 그래서 드라이버는 묶음 끝에 펜스라는 표시를 하나 달아 둡니다. GPU 가 거기까지 오면 펜스의 값이 바뀝니다.
장치가 CPU 에 「일이 생겼다」고 끼어들어 알리는 신호를 인터럽트라고 합니다. GPU 는 펜스에 이르면 인터럽트로 커널 쪽 드라이버에 알립니다. 아래 그림은 한 묶음이 넘어가고 끝났다는 알림이 돌아오기까지의 순서입니다.
sequenceDiagram
participant P as 프로그램
participant U as 사용자 공간 쪽
participant K as 커널 쪽
participant G as GPU
P->>U: 그리기 함수 호출
U->>U: GPU 명령으로 바꿔 커맨드 버퍼에 적는다
U->>K: 커맨드 버퍼와 펜스를 넘긴다
K->>G: 커맨드 버퍼 제출
Note over P,U: 프로그램은 기다리지 않고 다음 장면을 적는다
G-->>K: 펜스에 이르렀다는 인터럽트
K-->>P: 그 묶음이 끝났다
GPU 메모리 배분
GPU 는 그리는 데 쓸 자료를 가까이 둡니다. 그림 파일인 텍스처, 점의 좌표를 담은 버퍼, 그려진 결과가 모두 그런 자료입니다. 별도 그래픽 카드는 이 자료를 카드에 붙은 전용 메모리인 VRAM(Video Random Access Memory, 비디오 메모리)에 둡니다.
한 GPU 를 여러 프로그램이 같이 씁니다. 브라우저와 게임이 동시에 VRAM 을 원할 수 있습니다. 어느 자료를 어디에 둘지는 커널 쪽 드라이버가 정합니다. VRAM 이 모자라면 덜 쓰는 자료를 일반 메모리로 내려보냈다가 필요할 때 다시 올립니다.
자료를 내리고 올리는 동안 그리기가 느려집니다. 메모리가 끝내 모자라면 프로그램이 GPU 에 올려 둔 자료를 잃기도 합니다. 브라우저의 WebGL 은 이것을 컨텍스트 손실 이벤트로 알립니다.
화면으로 내보내는 일
그리기가 끝난 결과는 메모리의 한 구역에 픽셀로 남습니다. 모니터에 내보낼 한 장을 담는 이 구역을 프레임버퍼라고 합니다. 완성된 프레임버퍼를 모니터로 내보내는 일도 드라이버가 맡습니다.
모니터는 화면을 한 번 그리고 끝내지 않고 1초에도 여러 번 새로 그립니다. 주사율은 모니터가 1초에 화면을 몇 번 새로 그리는지를 나타냅니다. 주사율이 60 인 모니터는 1초에 60번 새로 그립니다.
모드 설정은 모니터에 맞춰 해상도와 주사율을 정하는 일입니다. 모니터를 새로 꽂거나 해상도를 바꾸면 드라이버가 이 일을 합니다.
프레임버퍼는 대개 두 장 이상 둡니다. 모니터가 한 장을 읽어 가는 동안 GPU 는 다른 장에 다음 화면을 그립니다. 다 그리면 두 장의 역할을 맞바꿉니다.
모니터는 한 장을 위에서 아래로 훑어 그린 뒤 다음 장을 시작하기 전에 잠깐 쉽니다. 이 틈을 vblank(vertical blanking, 수직 공백 구간)라고 합니다. 드라이버는 이 틈에 맞춰 두 장의 역할을 바꿉니다.
틈을 기다리지 않고 바꾸면 한 화면에 앞 장의 위쪽과 다음 장의 아래쪽이 섞여 보입니다. 이 현상을 티어링이라고 합니다.
멈춘 GPU 를 되살리는 재설정
GPU 가 끝나지 않는 셰이더 같은 이유로 응답하지 않을 때가 있습니다. 그러면 화면도 함께 멈춥니다. 운영체제는 GPU 가 응답할 시간을 정해 둡니다. 그 시간을 넘겨도 응답이 없으면 커널 쪽 드라이버에게 GPU 를 재설정하게 합니다.
재설정하면 GPU 에 올라 있던 자료가 사라질 수 있습니다. 기기를 다시 켜지 않고 화면을 되살리는 대가입니다. 그 GPU 를 쓰던 프로그램은 자료를 처음부터 다시 올려야 합니다. 앞에서 본 WebGL 의 컨텍스트 손실이 이때도 일어납니다.
드라이버마다 갈리는 동작
같은 API 호출도 드라이버마다 번역이 다릅니다. 그래서 같은 코드가 제조사나 드라이버 판에 따라 다르게 그려지거나 속도가 크게 차이 납니다. 제조사가 새 게임에 맞춰 드라이버를 고쳐 내놓기도 합니다.
드라이버의 버그는 그 위의 프로그램이 피하기 어렵습니다. 브라우저는 버그가 알려진 드라이버의 목록을 둡니다. 목록에 오른 드라이버에서는 GPU 를 쓰는 기능 일부를 끕니다.
WebGPU 를 구현한 브라우저는 페이지가 보낸 명령을 먼저 검사한 뒤에 드라이버로 넘깁니다. 웹 페이지가 드라이버의 허점을 건드려 기기를 멈추지 못하게 하려는 것입니다.
운영체제마다 붙은 이름
두 부분으로 나눈 구조는 같아도 운영체제마다 부르는 이름이 다릅니다. 표에 나올 이름 셋을 먼저 봅니다.
리눅스 커널에는 다이렉트 렌더링 매니저(Direct Rendering Manager, DRM)가 있습니다. GPU 드라이버들이 공통으로 쓰는 커널 안의 틀입니다. 메모리 배분이나 화면 출력처럼 GPU 마다 겹치는 일을 한데 모아 둡니다.
Mesa 는 리눅스의 사용자 공간 쪽 오픈소스 드라이버를 모은 프로젝트입니다.
WDDM(Windows Display Driver Model, 윈도우 디스플레이 드라이버 모델)은 Windows 에서 그래픽 드라이버가 따라야 하는 구조를 정해 둔 틀입니다.
| 운영체제 | 커널 쪽 | 사용자 공간 쪽 |
|---|---|---|
| 리눅스 | DRM 위에 얹힌 GPU 별 모듈 | Mesa 의 드라이버 또는 제조사 드라이버 |
| Windows | WDDM 의 커널 모드 드라이버 | WDDM 의 사용자 모드 드라이버 |
백엔드 개발자가 드라이버를 만나는 경우
서버에는 대개 모니터가 없어서 그래픽 드라이버와 무관해 보입니다. GPU 로 머신러닝 모델을 학습시키거나 추론을 돌리는 서버는 다릅니다. CUDA(Compute Unified Device Architecture) 같은 계산용 API 도 드라이버를 거쳐야 GPU 에 닿습니다.
이런 서버를 컨테이너로 돌리면 드라이버의 커널 쪽은 호스트에 있습니다. 컨테이너는 호스트의 커널을 같이 쓰기 때문입니다.
한편 컨테이너 이미지 안의 GPU 프로그램과 라이브러리는 특정 판 이상의 드라이버를 요구합니다. 그래서 호스트에 깔린 드라이버가 그보다 오래되면 컨테이너 안의 GPU 프로그램이 시작하지 못합니다.
GPU 가 없는 서버에서 헤드리스 브라우저로 페이지를 그리는 경우도 있습니다. 이때는 CPU 로 GPU 를 흉내 내는 소프트웨어 렌더러가 드라이버 노릇을 합니다. 그림은 나오지만 GPU 보다 훨씬 느립니다.
관련 항목
그래픽 드라이버가 속하는 상위 분류
디바이스 드라이버 · 운영체제 · 커널 · 사용자 공간 · 그래픽스
그래픽 드라이버가 구현하는 API
그래픽스 API · OpenGL · Vulkan · Direct3D · Metal · WebGL · WebGPU · CUDA · OpenCL
그래픽 드라이버가 명령을 넘기는 하드웨어
GPU · VRAM · 그래픽 카드 · 통합 그래픽 · 펌웨어 · DMA · 인터럽트
그래픽 드라이버가 번역하는 셰이더와 중간 형식
셰이더 · SPIR-V · 셰이더 캐시 · 셰이더 컴파일러 · 정점 셰이더 · 프래그먼트 셰이더 · 컴퓨트 셰이더 · 그래픽스 파이프라인
그래픽 드라이버가 GPU 에 일을 넘기는 장치
커맨드 버퍼 · 커맨드 서브미션 · 펜스 · 시스템 콜 · GPU 스케줄링
그래픽 드라이버가 화면으로 내보내는 단계
프레임버퍼 · 모드 설정 · vblank · 주사율 · 티어링 · 수직 동기화 · 더블 버퍼링
그래픽 드라이버에서 자주 나는 장애
컨텍스트 손실 · GPU 재설정 · TDR · 드라이버 블록리스트 · 커널 패닉
그래픽 드라이버를 구현한 운영체제별 소프트웨어
다이렉트 렌더링 매니저 · Mesa · WDDM · KMS · 소프트웨어 렌더러 · llvmpipe · SwiftShader
그래픽 드라이버 위에서 GPU 를 쓰는 서버 환경
다른 이름: graphics driver · GPU 드라이버 · 그래픽 카드 드라이버 · 디스플레이 드라이버