렌더링 파이프라인 완벽 이해 – CPU와 GPU는 어떻게 협력해서 화면 이미지를 만들까?

이전 글에서는 Output Merger 단계가 왜 필요한지를 정리했습니다.

Output Merger 단계에서는 Depth Test와 Blending,
그리고 Frame Buffer 갱신 같은 작업들이 수행되며,
최종적으로 GPU가 실제 화면 이미지를 완성하게 된다는 점도 함께 살펴봤습니다.

지금까지 렌더링 파이프라인 시리즈에서
Vertex 데이터를 조립하고,
공간 변환을 수행하며,
Fragment를 생성하고,
최종 색상을 계산한 뒤,
실제 화면 버퍼에 기록하는 과정까지 전체 흐름을 순서대로 정리했습니다.

Input Assembly
      ↓
Vertex Shader 
      ↓
 Rasterizer 
      ↓
Pixel Shader
      ↓
Output Merger

그리고 렌더링 파이프라인을 계속 공부하다 보면 자연스럽게 한 가지 궁금증이 생기기 시작합니다.

“이 모든 작업은 GPU가 혼자 처리하는 걸까?”

렌더링 파이프라인 자체는 GPU 내부에서 동작하는 거대한 계산 흐름(Flow)입니다.

하지만 실제 게임 엔진 내부에서는 CPU 또한 굉장히 많은 준비 작업을 수행합니다.

CPU는 어떤 객체를 렌더링할지 결정하고,
어떤 Mesh와 Material을 사용할지 관리하며,
GPU가 실제 렌더링을 수행할 수 있도록 다양한 데이터를 지속적으로 준비합니다.

그리고 GPU는 CPU가 전달한 데이터를 기반으로 대량 병렬 계산을 수행하면서 실제 화면 이미지를 만듭니다.

즉, 최신 렌더링 구조는 단순히 GPU가 단독 처리하는 구조가 아니라,
CPU와 GPU가 서로 역할을 나누어서 협력하는 구조입니다.

그리고 최근 게임 엔진 최적화를 이해하기 위해서도 결국 CPU와 GPU가 어떤 방식으로 역할을 분리하고,
어떤 지점에서 병목(Bottleneck)이 발생하는지를 함께 이해하는 것이 점점 더 중요해지고 있습니다.

렌더링 작업은 CPU와 GPU가 함께 만들어내는 예술입니다.


GPU는 혼자서 화면을 만들지 않는다

GPU는 매우 강력한 병렬 연산 장치입니다.

특히 수많은 Vertex와 픽셀을 동시에 계산하는 작업에 굉장히 특화되어 있습니다.

예를 들어 Vertex Shader 단계에서 대량의 Vertex를 변환하거나,
Pixel Shader 단계에서 수백만 개의 픽셀 색상을 계산하는 작업은 GPU가 매우 효율적으로 처리할 수 있습니다.

렌더링 파이프라인도 GPU 내부에서 동작하는 작업 공정입니다.

하지만 실제 게임 화면이 만들어지는 과정 전체를 보면 GPU 혼자서 모든 작업을 처리하는 것은 아닙니다.

GPU는 전달받은 데이터를 빠르게 계산하는 데 특화되어 있을 뿐,
어떤 객체를 화면에 그려야 하는지 스스로 판단하지는 않습니다.

예를 들어 현재 화면에 어떤 객체를 렌더링할지 결정하거나,
어떤 Texture와 Material을 사용할지 선택하고,
어떤 Shader를 사용할지를 관리하는 작업들은 대부분 CPU에서 수행합니다.

또한 게임 엔진 내부에서 처리되는 카메라 밖에 있는 객체를 제외하거나,
비활성 객체를 렌더링 목록에서 제거하는 작업 역시 CPU가 담당하게 됩니다.

그리고 CPU는 이렇게 정리된 렌더링 데이터를 GPU에게 전달합니다.

GPU는 CPU가 전달한 데이터를 기반으로
Vertex 변환,
Rasterizer 처리,
Pixel Shader 계산 같은 대량 병렬 연산을 수행합니다.

따라서 렌더링 구조는 CPU와 GPU가 서로 역할을 나누어서 협력하는 구조라고 할 수 있습니다.

CPU는 렌더링을 위한 준비와 제어를 담당하고, GPU는 실제 화면 계산을 담당하는 형태로 역할이 분리되어 있습니다.


CPU는 어떤 일을 할까?

게임 엔진에서 CPU는 단순히 렌더링 명령만 전달하는 역할을 담당하지 않습니다.

실제로 CPU는 게임 전체 흐름을 관리하고 제어하는 중심 역할에 가깝습니다.

게임 안에서는
캐릭터 이동과 물리 충돌 처리,
애니메이션 업데이트,
AI 판단,
입력 처리 같은 다양한 작업들이 지속적으로 동시에 처리됩니다.

그리고 이런 작업 상당수는 CPU가 맡아서 처리합니다.

따라서 CPU는 게임 전체 동작을 관리하는 역할을 담당한다고 볼 수 있습니다.

렌더링 측면에서도 CPU는 굉장히 중요한 역할을 담당합니다.

GPU는 전달받은 데이터를 빠르게 계산하는 데 특화되어 있지만,
어떤 객체를 실제 화면에 그려야 하는지 스스로 판단하지는 않습니다.

예를 들어 현재 카메라 위치 기준으로 어떤 객체가 화면 안에 존재하는지,
어떤 객체를 렌더링 대상에서 제외해야 하는지 같은 판단은 CPU에서 이루어집니다.

카메라 밖에 있는 객체는 렌더링 목록에서 제거할 수 있고, 비활성 객체 역시 렌더링 대상에서 제외할 수 있습니다.

또한 어떤 Mesh를 사용할지,
어떤 Material과 Texture를 사용할지,
어떤 Shader를 사용할지도 관리합니다.

그리고 이렇게 정리된 렌더링 정보를 GPU에게 전달하는 역할 역시 CPU가 담당합니다.

Draw Call을 생성하고,
GPU 상태(State)를 변경하며,
필요한 Buffer와 Shader를 바인딩하는 작업들도 CPU에서 수행됩니다.

즉, CPU는 GPU가 실제 렌더링 계산을 수행할 수 있도록 필요한 데이터를 준비하고 관리하는 역할을 담당한다고 볼 수 있습니다.

최근 게임 엔진 최적화에서도 CPU 작업량은 굉장히 중요한 요소가 되었습니다.

왜냐하면 CPU가 렌더링 준비 작업을 충분히 빠르게 수행하지 못하면,
GPU가 아무리 빠르더라도 CPU가 준비 작업을 완료하기까지 대기해야 하기 때문입니다.

따라서 최근의 렌더링 구조에서는 GPU의 성능뿐 아니라,
CPU가 얼마나 효율적으로 렌더링 데이터를 준비할 수 있는지도 매우 중요합니다.


GPU는 어떤 일을 할까?

지금까지 살펴본 대로 GPU는 CPU가 전달한 데이터를 기반으로 실제 렌더링 계산을 수행하게 됩니다.

예를 들어 Vertex Shader 단계에서 Vertex 변환을 수행하고,
Rasterizer 단계에서는 Fragment를 생성하게 됩니다.

이후 Pixel Shader 단계에서는 최종 픽셀 색상을 계산하고,
Output Merger 단계에서는 Depth Test와 Blending 같은 작업들을 수행하면서 실제 화면 이미지를 완성하게 됩니다.

즉, 렌더링 파이프라인의 계산 작업은 GPU 내부에서 수행됩니다.

특히 GPU는 동일한 연산을 엄청난 수로 반복 처리하는 작업에 매우 특화되어 있습니다.

화면 위에는 수백만 개의 픽셀이 존재할 수 있습니다. GPU는 이 픽셀들 각각에 대해 동시에 조명 계산과 Texture Sampling, Material 계산 같은 작업들을 병렬로 처리할 수 있습니다.

또한 수많은 Vertex를 동시에 변환하거나, 대량의 Fragment를 병렬로 처리하는 작업 역시 GPU가 매우 효율적으로 수행할 수 있습니다.

이런 이유 때문에 GPU 내부에는 동일한 연산 장치가 대량으로 배치되어 있습니다.

CPU가 상대적으로 적은 수의 강력한 코어를 사용하는 구조라면,
GPU는 훨씬 더 많은 수의 연산 유닛을 이용해서 대량 병렬 계산에 최적화된 구조를 가집니다.

따라서 GPU는 복잡한 제어 로직보다는, 동일한 계산을 반복 수행하는 작업에서 압도적인 성능을 발휘하게 됩니다.

이런 구조 덕분에 GPU는 수백만 개의 픽셀 계산과 대량 Vertex 처리, 그리고 복잡한 Shader 연산까지 실시간으로 수행할 수 있게 되었습니다.

예를 들어 최근 게임에서는 화면 전체에 대해 실시간 조명 계산과 반사 표현,
후처리 효과 같은 작업들이 계속 수행됩니다.

그리고 이런 계산들은 대부분 픽셀 단위로 반복적으로 이루어지게 됩니다.

GPU는 바로 이런 대량 반복 계산에 최적화되어 있기 때문에,
게임 그래픽 같은 복잡한 화면도 실시간으로 처리할 수 있게 된 것입니다.

결국 최신의 그래픽 구조에서는 CPU와 GPU가 서로 다른 역할에 최적화되어 있다고 볼 수 있습니다.

CPU는 게임 로직과 렌더링 준비 작업 같은 제어 중심 작업을 담당하고,
GPU는 실제 화면 생성을 위한 대량 병렬 계산을 담당하게 됩니다.

따라서 최근의 게임 엔진 구조 역시 CPU와 GPU가 서로 어떤 작업을 분담하는 것이 가장 효율적인지를 중심으로 계속해서 발전하고 있다고 할 수 있습니다.


Draw Call이 중요한 이유

CPU와 GPU 협력 구조를 이야기할 때 반드시 등장하는 개념이 바로 Draw Call입니다.

Draw Call은 CPU가 GPU에게 렌더링 작업을 요청하는 명령입니다. Draw Call이라는 말 그대로 “Draw 함수를 호출”합니다.

CPU는 어떤 Mesh 데이터를 사용할지 결정하고,
어떤 Texture와 Material을 사용할지 설정합니다.

또한 어떤 Shader를 사용할지, 어떤 Buffer를 사용할지도 함께 준비합니다.

그리고 이렇게 렌더링에 필요한 상태(State)가 모두 준비되면 CPU는 GPU에게 렌더링 명령을 전달합니다.

이 과정이 바로 Draw Call입니다.

GPU는 Draw Call을 전달받은 뒤, CPU가 설정한 각종 정보를 기반으로 Vertex Shader와 Rasterizer, Pixel Shader 같은 렌더링 파이프라인 계산을 수행합니다.

즉, Draw Call은 CPU와 GPU 사이를 연결하는 렌더링 요청 단위라고 생각할 수 있습니다.

문제는 Draw Call 자체에도 비용이 존재한다는 점입니다.

CPU가 GPU에게 렌더링 명령을 전달하는 과정 역시 일정한 처리 비용이 필요하기 때문입니다.

예를 들어 화면 안에 수천 개의 객체가 존재하고,
각 객체마다 Draw Call이 하나씩 발생한다고 가정해보겠습니다.

이 경우 GPU 계산 이전에 CPU가 엄청난 수의 렌더링 명령을 계속 준비하고 전달해야 하는 상황이 발생할 수 있습니다.

그리고 Draw Call 수가 지나치게 많아지면 CPU 병목(CPU Bottleneck)이 발생할 가능성도 함께 증가하게 됩니다.

따라서 최근 게임 엔진들은 Draw Call 수를 줄이기 위한 다양한 최적화 기법들을 적극적으로 사용합니다.

예를 들어 동일한 Mesh를 여러 번 렌더링할 때는 GPU Instancing을 사용해서 여러 객체를 한 번의 Draw Call로 처리하기도 합니다.

또한 여러 객체를 하나의 큰 Mesh처럼 묶어서 렌더링하는 Batch Rendering 같은 기법들도 사용됩니다.

Deferred Rendering 구조 역시 단순히 그래픽 표현 방식 변화만 의미하는 것이 아니라, CPU와 GPU 사이의 렌더링 비용 문제와 관련되어 있습니다.

결국 최근 게임 엔진 최적화에서 Draw Call은 단순 렌더링 명령 이상의 의미를 가지게 되었습니다.

CPU가 GPU에게 얼마나 효율적으로 렌더링 작업을 전달할 수 있는지 역시 렌더링 성능에서 굉장히 중요한 요소이기 때문입니다.


CPU 병목이 발생하는 이유

게임 성능을 분석하다 보면 GPU 사용률은 높지 않은데도 FPS가 충분히 나오지 않는 상황을 보게 되는 경우가 있습니다.

이런 상황에서는 CPU 병목(CPU Bottleneck)이 발생했을 가능성이 있습니다.

GPU는 매우 빠른 병렬 계산 장치이지만,
CPU가 렌더링 준비 작업을 충분히 빠르게 수행하지 못하면 GPU 역시 계속 작업을 진행할 수 없기 때문입니다.

다시 말해, GPU가 아무런 작업을 하지 않고 대기 상태(노는 상태)가 되는 것입니다.

예를 들어 CPU는 매 프레임 어떤 객체를 렌더링할지 판단하고,
Draw Call을 생성하며, 필요한 상태(State)를 설정하는 작업들을 계속 수행합니다.

그리고 GPU는 CPU가 전달한 명령을 기반으로 실제 렌더링 계산을 처리합니다.

문제는 CPU 준비 작업이 지나치게 많아지는 경우입니다.

Draw Call 수가 너무 많아지거나,
게임 로직 계산과 물리 연산,
애니메이션 업데이트 같은 작업들이 과도하게 증가하면 CPU가 병목 지점이 될 수 있습니다.

이 경우 GPU는 충분한 계산 성능이 남아 있더라도,
CPU가 다음 렌더링 명령을 준비할 때까지 대기 상태에 들어가게 됩니다.

따라서 GPU 성능이 남아 있어도 CPU가 따라오지 못하면 전체 프레임 속도는 제한될 수 있습니다.

최근 게임 엔진에서는 대규모 오브젝트 처리와 복잡한 AI 계산,
실시간 물리 연산,
수많은 Draw Call 처리 같은 작업들이 계속 증가하고 있습니다.

그리고 이런 흐름 때문에 CPU 최적화 역시 점점 더 중요해지고 있습니다.

결국 게임 성능은 단순 GPU 성능만으로 결정되지 않습니다.

CPU와 GPU가 얼마나 균형 있게 협력하고 있는지,
그리고 어느 한쪽이 병목 지점이 되고 있지는 않은지가 굉장히 중요해졌습니다.

따라서 최근 게임 엔진 최적화에서는 GPU 최적화뿐 아니라,
CPU 작업량을 줄이고 Draw Call 비용을 감소시키는 작업 역시 중요하게 다룹니다.


GPU 병목이 발생하는 이유

반대로 GPU 병목(GPU Bottleneck)이 발생하는 경우도 존재합니다.

CPU가 렌더링 데이터를 충분히 빠르게 준비하고 있더라도,
GPU가 실제 화면 계산을 모두 끝내지 못해서 프레임 속도가 떨어지는 경우가 발생할 수 있습니다.

해상도가 지나치게 높아지면 GPU가 처리해야 하는 픽셀 수 자체가 크게 증가합니다.

특히 4K 같은 고해상도 환경에서는 Full HD보다 훨씬 더 많은 픽셀 계산이 필요합니다.

또한 Pixel Shader 계산이 복잡해질수록 GPU 부하 역시 함께 증가합니다.

최근 게임 그래픽에서는 실시간 조명 계산과 반사 표현,
복잡한 Material 계산,
후처리 효과 같은 작업들이 적극적으로 사용됩니다.

그리고 이런 계산의 상당수는 픽셀 단위로 반복 수행됩니다.

예를 들어 Bloom이나 Motion Blur,
Screen Space Reflection 같은 후처리 효과들은 화면 전체 픽셀에 대해 추가 계산을 수행합니다.

따라서 후처리 효과가 많아질수록 GPU 계산량 역시 크게 증가할 수 밖에 없습니다.

최근 게임 그래픽에서 사용되는 Ray Tracing과 PBR,
Global Illumination 같은 기술들도 GPU 부하 증가와 관련이 있습니다.

이런 기술들은 훨씬 더 사실적인 화면 표현을 가능하게 만들었지만,
동시에 GPU가 처리해야 하는 계산량 역시 크게 증가시켰습니다.

그리고 GPU 계산량이 GPU의 처리 속도를 초과하면 프레임 속도가 떨어집니다.

이 경우 CPU는 충분히 빠르게 렌더링 데이터를 준비하더라도,
GPU가 픽셀 계산을 모두 끝낼 때까지 기다려야 하는 상황이 발생할 수 있습니다.

따라서 최근 게임 엔진 최적화에서는 CPU 최적화만 중요한 것이 아닙니다.

GPU에서 수행되는 Shader 계산량과 후처리 비용,
해상도에 따른 픽셀 처리량 같은 요소들도 함께 최적화하는 것이 점점 더 중요해지고 있습니다.


화면 이미지는 CPU와 GPU가 협력하면서 만든다

여기까지 흐름을 정리해보면 화면 이미지를 만들어 내는 과정이 GPU에 의해서만 이루어지는 것이 아니라는 점을 이해할 수 있습니다.

렌더링 파이프라인 자체의 동작은 GPU가 담당하지만, 이 파이프라인이 제대로 동작하기 위해서는 CPU의 도움이 필수적이라는 것도 알게되었습니다.

CPU는 어떤 객체를 렌더링할지 판단하고,
필요한 Mesh와 Material,
Texture,
Shader 정보를 준비합니다.

그리고 Draw Call을 생성해서 GPU에게 렌더링 명령을 전달하게 됩니다.

GPU는 CPU가 전달한 데이터를 기반으로 Vertex 변환과 Rasterizer 처리,
Pixel Shader 계산 같은 대량 병렬 연산을 수행하면서 실제 화면 이미지를 만듭니다.

즉, 이러한 렌더링 구조는 단순하게 GPU 단독 계산 흐름이라기보다,
CPU가 렌더링 데이터를 준비하고 GPU가 실제 화면 계산을 수행하는 협력 구조라는 것을 알 수 있습니다.

그리고 최근 게임 엔진들은 이 협력 구조를 최대한 효율적으로 만들기 위해 계속 발전하고 있습니다.

예를 들어 최근 엔진들이 ECS(Entity Component System)에 관심을 가지는 이유 역시 CPU 데이터 처리 효율과 관련되어 있습니다.

또한 GPU Instancing 같은 기술은 동일한 객체를 여러 번 렌더링할 때 Draw Call 수를 줄이기 위해 사용됩니다.

Multithread Rendering 역시 CPU가 렌더링을 준비하는 작업을 여러 스레드로 병렬 처리해서 GPU에게 데이터를 더 빠르게 전달하기 위해 등장했다고 할 수 있습니다.

결국 최근 게임 엔진 구조와 렌더링 최적화 기술의 상당수는 CPU와 GPU가 얼마나 효율적으로 협력할 수 있는지를 중심으로 발전하고 있다는 것을 알 수 있습니다.

그리고 이러한 발전의 이유 및 과정을 이해하면,
최근 게임 엔진 구조와 그래픽 최적화 발전의 흐름 역시 훨씬 자연스럽게 이해됩니다.


게임 엔진 최적화는 결국 CPU와 GPU의 균형 문제다

게임 최적화를 이야기할 때 많은 경우 GPU 성능부터 떠올리게 됩니다.

실제로 그래픽 품질과 해상도, 후처리 효과 같은 요소들은 GPU 부하와 직접적으로 연결되어 있기 때문입니다.

하지만 게임 엔진 최적화에서는 GPU 성능만 중요한 것이 아닙니다.

CPU와 GPU가 얼마나 균형 있게 협력하고 있는지도 굉장히 중요합니다.

예를 들어 CPU가 렌더링 준비 작업을 충분히 빠르게 수행하지 못하면 GPU는 다음 작업을 기다리게 됩니다.

반대로 CPU는 충분히 빠르게 데이터를 준비하고 있더라도,
GPU 계산량이 지나치게 많아지면 화면 출력 자체가 지연될 수 있습니다.

즉, CPU와 GPU 중 어느 한쪽만 과도하게 바빠져도 전체 프레임 속도는 감소할 수 있습니다.

따라서 최근 게임 엔진들은 CPU와 GPU 사이의 작업 균형을 최대한 효율적으로 맞추기 위해 다양한 최적화 기법들을 사용합니다.

Draw Call 수를 줄여서 CPU 부담을 감소시키고,
렌더링 작업 자체를 여러 스레드로 병렬 처리해서 CPU 준비 속도를 높입니다.

또한 GPU 측면에서는 Shader 계산량을 줄이거나,
후처리 효과를 최적화하고,
불필요한 픽셀 계산을 감소시키는 방식으로 GPU 부하를 줄입니다.

데이터 최적화 역시 CPU와 GPU 효율 모두와 관련되어 있습니다.

예를 들어 최근 엔진들이 ECS(Entity Component System)에 관심을 가지는 이유 역시 CPU Cache 효율과 병렬 처리 효율을 높이기 위해서라고 볼 수 있습니다.

결국 게임 엔진 최적화는 단순히 그래픽 품질을 낮추는 작업이 아닙니다.

CPU와 GPU 사이의 작업 흐름을 얼마나 효율적으로 구성할 수 있는지,
그리고 어느 한쪽이 병목 지점이 되지 않도록 어떻게 균형을 맞출 것인지에 훨씬 가까운 문제입니다.


마무리

지금까지 렌더링 파이프라인 각 단계가 왜 필요한지를 순서대로 정리해보았습니다.

그리고 이번 글에서는 CPU와 GPU가 실제로 어떤 방식으로 역할을 나누면서 화면 이미지를 만들어내는지도 함께 살펴보았습니다.

GPU는 강력한 병렬 계산 장치이지만, 혼자서 렌더링을 완성하지 않습니다.

CPU가 렌더링 데이터를 준비하고, GPU가 픽셀 계산을 수행하는 형태로 서로 협력합니다.

그리고 최근에 사용되는 게임 엔진 구조와 렌더링 최적화 기법의 상당수는 결국 CPU와 GPU 협력 관계 속에서 발전하고 있다고 볼 수 있습니다.


👉 게임 엔진 구조를 더 깊이 이해하고 싶다면

아래 강의를 통해 직접 구현해보는 것을 추천드립니다.

C++로 만드는 게임 엔진 프레임워크 강의

👉 C++로 만드는 게임 엔진 프레임워크 강의 바로가기

댓글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다