들어가며
최근 흥미로운 글 하나를 읽었습니다.
바로 Sebastian Aaltonen의 “No Graphics API“라는 글입니다.
제목만 보면 마치 그래픽스 API 자체가 필요 없다는 주장처럼 보이기도 합니다.
하지만 실제 내용은 조금 다릅니다.
이 글은 아래와 같이 현대의 Graphics API의 발전 과정과 왜 이렇게 복잡하게 설계되었는지를 깊게 다룹니다.
- 왜 DX12/Vulkan/Metal 같은 저수준 API가 등장했는지
- 당시에는 왜 그런 복잡도가 필요했는지
- 그런데 왜 지금은 오히려 복잡도가 문제가 되는지
- 현대 GPU 구조는 어떻게 바뀌고 있는지
읽으면서 개인적으로 최근 Unity SRP, RenderGraph, Bindless Rendering 과도 연결되는 부분이 많다고 느꼈습니다.
이번 글에서는 원문 내용을 기반으로 현대 그래픽스 API가 왜 이렇게 복잡해졌는지,
그리고 GPU 구조가 어떤 방향으로 발전하고 있는지 정리해보겠습니다.
DX11/OpenGL 시대의 그래픽스 API
DX11/OpenGL 시절 그래픽스 API는 상당히 “높은 수준”의 API였습니다.
예를 들어 DX11에서는 드로우 콜을 하면 드라이버가 내부적으로 굉장히 많은 작업을 처리했습니다.
context->DrawIndexed(...);
- 리소스 상태 관리
- 동기화 처리
- 메모리 배치
- 커맨드 최적화
- 드라이버 레벨 batching
등 많은 작업을 API가 처리해줬습니다.
개발자는 상대적으로 편했지만, 드라이버가 굉장히 많은 일을 대신 처리했습니다.
문제는 이 구조가 점점 한계를 드러내기 시작했다는 점입니다.
DX11/OpenGL 시대의 문제
대표적인 문제는 CPU 오버헤드였습니다.
당시 GPU는 점점 빨라지고 있었지만, CPU는 드로우콜 처리 비용 때문에 병목이 생기기 시작했습니다.
예를 들어, 수만 개 draw call / state 변경 / resource binding과 같은 작업이 늘어나면서, 드라이버 내부 비용이 굉장히 커졌습니다.
즉, GPU는 더 많은 작업 가능이 가능하지만, CPU가 draw call 준비하다가 병목이 발생하는 구조가 된 것입니다.
이 상태에서는 GPU가 일을 할 수 있는 상태더라도 더 좋은 성능을 낼 수가 없습니다.
(예전 포스팅 참고)
특히, 멀티코어 CPU 시대가 되면서 이 문제는 더 커졌습니다.
왜냐하면 DX11/OpenGL 드라이버는 상당 부분 싱글 스레드에 친화적인(single-thread friendly) 구조였기 때문입니다.
그래서 등장한 DX12 / Vulkan / Metal
이 문제를 해결하기 위해 등장한 것이 저수준(low-level) 그래픽스 API입니다.
- DirectX 12
- Vulkan
- Metal
저수준 API의 핵심 목표는 드라이버가 하던 일을 개발자에게 넘겨서 CPU 오버헤드를 줄이자는 것이었습니다.
즉, 기존에는 드라이버가 자동으로 처리하던 작업들을, 이제는 개발자가 직접 관리하게 됩니다.
복잡도는 증가했지만, 대신 CPU 비용을 줄이고 멀티쓰레드 활용이 가능해졌습니다.
- resource barrier
- descriptor heap
- pipeline state object
- command buffer
- synchronization
왜 당시에는 저수준 API가 필요했을까?
당시 기준으로는 이 복잡도가 어느 정도 합리적이었습니다.
왜냐하면 GPU 제조사마다 구조 차이가 굉장히 컸기 때문입니다.
예를 들면, 아래와 같이 GPU 제조사마다 차이가 나는 부분이 많았습니다.
- 메모리 구조
- resource binding 방식
- cache 구조
- pipeline 처리 방식
이에 따라서 드라이버가 모든 것을 추상화하는 비용이 커지고 있었습니다.
그래서 API 자체를 더 낮은 수준으로 내려서, 개발자가 GPU에 더 직접 접근하게 만든 것입니다.
결과적으로 드라이버 비용 감소 / 멀티쓰레드 recording 가능 / GPU 친화적 구조를 얻을 수 있었습니다.
실제로 DX12/Vulkan은 대규모 렌더링 성능 개선에 꽤 큰 영향을 주었습니다.
그런데 왜 지금은 복잡도가 문제가 될까?
지금까지 설명한 DX11/OpenGL 시대와 DX12/Vulkan/Metal 시대의 구조 차이, 그리고 현대 GPU 구조 변화를 그림으로 정리해보면 다음과 같습니다.
핵심은 GPU는 점점 단순한 메모리 접근 구조로 발전하고 있지만, 그래픽스 API는 여전히 복잡한 상태 관리 구조를 유지하고 있다는 점입니다.

하지만, 시간이 지나면서 상황이 점차 바뀌기 시작합니다.
Sebastian Aaltonen은 여기서 현대 GPU 구조가 점점 비슷해지고 있다는 점을 이야기합니다.
애초에 Graphics API가 많은 작업을 추상화해 처리했던 이유는 GPU 제조사들 마다 구조가 서로 달랐기 때문이었습니다.
그런데 최근 GPU들은 아래의 나열된 구조를 점점 공통적으로 지원하기 시작했습니다.
- Bindless Resource
- 64bit GPU pointer
- Unified Memory 접근
- CPU visible GPU memory
예전처럼 GPU마다 구조 차이가 극단적으로 크지 않다는 것입니다.
그런데 문제는 그래픽스 API는 여전히 굉장히 복잡한 상태라는 점입니다.
GPU는 점점 단순한 접근이 가능해지고 있지만 API는 여전히 복잡한 구조가 된 것입니다.
PSO(Pipeline State Object)란?
현대 그래픽스 API에서 가장 대표적인 구조 중 하나가 바로 PSO입니다.
DX12/Vulkan에서는 아래와 같은 렌더링 상태를 미리 묶어서 관리합니다.
- Shader
- Blend State
- Depth State
- Rasterizer State
- Vertex Layout
이걸 Pipeline State Object라는 하나의 구조로 관리합니다.
이 구조의 목적은 드로우콜 시점 비용을 줄이는 것입니다.
즉, 렌더링 전에 상태를 미리 컴파일/준비해두는 개념입니다.
그런데 PSO Explosion 문제가 생긴다
문제는 현대 엔진은 그 조합 수가 굉장히 많다는 점입니다.
예를 들면, 아래에 나열된 설정 값들이 조합되기 시작합니다.
- Shadow On/Off
- Skinning On/Off
- Instancing On/Off
- Transparent / Opaque
- MSAA 여부
그러면 PSO 개수가 폭발적으로 증가할 수 있습니다.
2 x 2 x 2 x 2 x 2 ...
이걸 흔히 PSO Explosion이라고 부릅니다.
CPU 최적화를 위해 만든 구조가, 오히려 엄청난 permutation 문제를 만드는 것입니다.
실제로 최신 엔진에서는 PSO cache 관리가 굉장히 큰 문제가 되기도 합니다.
Bindless 구조란?
현대 GPU에서 굉장히 중요한 변화 중 하나가 바로 Bindless Resource 구조입니다.
기존 방식에서는 렌더링 전에 리소스를 슬롯에 bind해야 했습니다.
예를 들어, 텍스처를 그리팩카드에서 그려야하는 경우 그리기 전에 아래와 같이 바인딩을 해야했습니다.
SetTexture(slot, texture);
즉, CPU가 계속 리소스를 연결해야 했습니다.
하지만 bindless 구조에서는 GPU가 직접 리소스를 참조할 수 있습니다.
resource index -> GPU descriptor 접근 -> 직접 resource fetch 구조가 가능합니다.
이건 굉장히 큰 변화입니다.
Bindless가 무엇을 바꾸었을까?
Bindless 구조가 등장하면서 CPU가 매 draw call마다 binding을 반복할 필요가 줄어듭니다.
이를 통해 아래와 같은 효과를 얻을 수 있습니다.
- descriptor binding 감소
- draw call 비용 감소
- GPU driven rendering 강화
그리고 이 구조는 최근 엔진 흐름과도 강하게 연결됩니다.
- Nanite
- Mesh Shader
- GPU Driven Rendering
- Bindless Material
등등
이러한 특징은 렌더링 구조가 점점 CPU 중심 → GPU 중심으로 이동하고 있다는 점을 보여주고 있습니다.
결국 현대 GPU는 어디로 가고 있을까?
Sebastian Aaltonen의 글에서 흥미로운 부분은 GPU가 점점 “일반 메모리“처럼 변하고 있다는 점입니다.
예전에는 GPU 메모리와 CPU 메모리의 구조 차이가 굉장히 컸습니다.
하지만, 최근에는 그 구조가 발전하고 있습니다.
- Unified Memory
- CPU mapped GPU memory
- GPU virtual address
이를 통해, GPU 메모리 접근 방식이 점점 단순화되고 있습니다.
그래서 Sebastian Aaltonen은 미래에는 지금처럼 복잡한 그래픽스 API가 필요하지 않을 수도 있다고 이야기합니다.
유니티 / 언리얼 엔진과도 연결되는 흐름
이 흐름은 유니티/ 언리얼 엔진과도 연결됩니다.
최근 엔진들은 아래와 나열한 것과 같이 GPU에 집중된 구조를 점점 더 강화하고 있습니다.
- RenderGraph
- GPU Driven Rendering
- Bindless Rendering
- Virtualized Geometry
CPU가 draw call을 하나씩 준비하는 시대에서, GPU가 더 많은 렌더링 구조를 직접 관리하는 방향으로 이동하고 있습니다.
그리고 이런 변화는 단순 엔진 기능 변화가 아니라, GPU 구조 자체 변화와 연결되어 있습니다.
핵심 정리
- DX11/OpenGL 시대에는 드라이버가 많은 작업을 자동 처리했다
- CPU 오버헤드 문제 때문에 DX12/Vulkan/Metal 같은 저수준 API가 등장했다
- 당시에는 GPU 구조 차이가 커서 저수준 API가 필요했다
- 하지만 현대 GPU는 구조가 점점 비슷해지고 있다
- PSO 구조는 CPU 비용 감소를 위해 등장했지만 permutation explosion 문제를 만든다
- Bindless Resource는 CPU binding 비용을 줄인다
- 현대 렌더링은 CPU 중심에서 GPU 중심 구조로 이동하고 있다
마무리
개인적으로 이 글이 흥미로웠던 이유는 단순히 DX12/Vulkan 사용법을 설명하는 글이 아니었기 때문입니다.
오히려 “왜 그래픽스 API가 이렇게 복잡해졌는가?” 그리고 “GPU 구조는 앞으로 어디로 가고 있는가?”를 설명하는 글에 가까웠습니다.
특히, 최근 아래에 나열된 키워드가 점점 더 중요해지는 이유도, 결국 이런 GPU 구조 변화와 연결되어 있다고 생각합니다.
- RenderGraph
- GPU Driven Rendering
- Bindless
- Mesh Shader
원문은 훨씬 더 깊고 하드웨어 수준 이야기까지 다루고 있으니, 관심 있는 분들은 원문도 꼭 읽어보시길 추천드립니다.