그래픽스

컴퓨터 그래픽스 - 드로우 콜(Draw Call)과 배칭(Batching)

오의현 2024. 5. 21. 20:59

컴퓨터 렌더링 최적화에 관한 정보를 찾으면, '드로우 콜'이라는 용어를 시도 때도 없이 만나게 될 것이다.

드로우 콜이라는 것은 CPU 병목의 주 원인으로 꼽히며, 게임 최적화에서 빼놓을 수 없는 존재이다.

 

드로우 콜에 대해 알아보자.

 

드로우 콜

먼저, 드로우 콜이라는 것은 간단하게 말하자면, CPU가 GPU에게 렌더링을 명령하는 것이다.

 

DirectX, Open GL, Vulkan 같은 그래픽스 API를 사용해서 게임을 만들게 되면, 렌더링을 할 때 그래픽카드의 도움을 받아서 연산을 진행하게 된다. DirectX의 경우 그래픽카드의 연산을 활용하기 위해, DeviceContext에 쉐이더도 세팅하고 렌더타겟도 세팅하고 뷰포트토 세팅하고 많은 세팅을 하게 된다. 그렇게 그래픽카드에 여러 명령을 보내서 렌더링을 실행하게 된다. 

 

드로우 콜의 과정을 알아보자.

 

먼저, 우리는 API에서 제공해주는 기능을 토대로 버퍼와 쉐이더를 생성하게 된다. 그 과정에서, GPU의 메모리에 해당 정보가 저장되게 된다. 버텍스 버퍼, 인덱스 버퍼, 픽셀 쉐이더, 뎁스 스텐실 등의 모든 정보가 GPU메모리 상에 저장되는 것이다.

 

GPU는 이 정보들과 RenderState를 기준으로 렌더링을 진행하게 된다. 

 

RenderState란?

: 어떤 대상을 어떻게 그릴지에 대한 정보를 저장하고 있는 테이블.

 

예를 들어, A라는 캐릭터를 툰 쉐이더를 이용해 그리고자 한다고 해보자. 그렇다면, A의 버텍스 버퍼, 인덱스 버퍼, 사용할 텍스쳐, 사용할 버텍스 쉐이더, 픽셀 쉐이더, 알파블렌딩, 뎁스 스텐실 등등 수많은 정보가 필요하다.

 

이미 모든 정보는 GPU의 메모리에 저장되어 있지만, 지금 어떤 것을 이용해 그릴 지는 알 수가 없다.

그 것에 대한 정보를 저장하고 있는 것이 RenderState이다.

 

위의 그림처럼, 현재 렌더링을 실행할 때 어떤 것을 사용하여 렌더링을 할 지에 대한 정보를 담고 있는 것이 Render State이며, Render State의 각 값은 GPU 메모리의 주소값으로 보유하고 있게 된다.

 

그렇다면, Render State에서 현재 어떤 것을 가지고 렌더링을 실행할 것인가는 어떻게 정해지는 것일까?
CPU에서 명령을 하게 된다.

 

위처럼, 렌더링를 하기 전에 우리는 버텍스 버퍼를 세팅하고, 버텍스 쉐이더를 세팅하고, 픽셀 쉐이더를 세팅하는 등 현재 렌더링에서 사용되어야 할 머티리얼을 세팅해준다. 

 

이러한 함수들이 그래픽카드의 Render State를 바꾸라는 명령인 것이다.

이렇게 Render State를 의도한 대로 모두 변경하고 나면, Direct X에선 Draw함수를 실행하게 된다. (Draw, DrawAuto, DrawIndexed..)

 

해당 함수는 렌더링 세팅이 완료 되었으니, 이제 그림을 그려라! 라는 명령인 것이다.

이 명령을 DP Call 이라고 한다. (Draw Primitive Call)

 

DP Call 명령은 즉각적으로 GPU에서 처리하는 것은 아니고, CPU의 Command Buffer에 명령어를 저장해놓으면 GPU에선 명령 처리가 가능할 때, 가장 앞의 명령어를 꺼내서 실행하는 방식이다.

(GPU가 기존 작업을 끝낼 때까지 CPU가 대기할 필요도 없고, GPU도 명령받은 작업들을 효율적으로 처리할 수 있다.)

 

이처럼, Render State 변경 명렁을 내리고 DP Call을 하는 것을 통틀어 Draw Call이라고 부르는 것이다.

 

드로우 콜은 기본적으로 CPU에서 GPU로 보내는 명령이기 때문에, 명령을 GPU가 알아듣도록 변환할 필요가 있다. 이 과정에서 아주 많은 오버헤드가 발생한다고 한다.

 

드로우 콜의 오버헤드를 줄이는 법

많은 사람들이 착각하는 것중 하나가 한 메쉬의 버텍스 개수가 적어지면 드로우 콜의 오버헤드가 완화될 것이라고 기대하는 것이다. 앞에서 말했듯이 버텍스 버퍼는 이미 GPU 메모리에 저장되어 있고, 포인터를 이용해서 Render State를 변경하게 된다.

 

즉, CPU에서 GPU로 데이터를 보내는 과정은 버텍스의 개수와는 큰 관련이 없고, GPU에서 렌더링 연산을 수행하는 것과 연관이 있는 것이다. 버텍스의 개수는 CPU의 연산 부담을 늘리는 것이 아니라 GPU의 연산 부담을 늘리는 것이다

 

.드로우 콜의 오버헤드를 줄이기 위해선, 텍스쳐 퀄리티를 낮춘다거나 버텍스의 개수를 낮추는 등의 작업이 필요한 것이 아니라 드로우 콜 자체의 횟수를 줄이는 것이 중요하다.

 

드로우 콜은 무언가를 그려야 할 때마다 호출하게 된다. 예를 들어, 메쉬 3개로 이루어진 캐릭터는 3번의 드로우콜을 호출해야 하며, 반대로 메쉬가 1개인 캐릭터라도 외곽선 등의 추가적인 효과를 적용하기 위해 2번 이상 드로우 콜을 호출할 수도 있다.

 

즉, 메쉬의 개수 혹은 머티리얼의 개수에 따라 드로우 콜의 호출 빈도가 결정되는 것이다.

그렇다면, 무작정 매쉬의 수를 줄이거나 머티리얼의 수를 줄여버리면 될까?

물론 아니다. 그래픽 퀄리티를 조금이라도 올리기 위해 하드웨어 성능을 극한까지 사용하는 사람들도 있는 와중에, 매쉬와 머티리얼을 줄여서 퀄리티를 포기한다는 것은 올바른 해결법이 아니다.

 

그렇다면, 여러 개의 메쉬를 한 번의 드로우 콜로 해결할 수 있다면?
여러 번 그려야 하는 것을 한 번의 드로우 콜로 해결하고자 하는 것이 배칭이다.

 

배칭

하나의 캐릭터 모델을 눈 메쉬, 머리 메쉬, 상체 메쉬, 하의 메쉬로 쪼개놓았다고 해보자.

 

그렇다면, 4개의 메쉬는 다른 머티리얼을 쓸까? 물론 그런 상황도 있겠지만, 일반적으로는 모두 같은 머티리얼을 사용해서 렌더링하게 된다. 차이가 있다면, 텍스쳐는 모두 다른 텍스쳐를 사용할 것이다.

 

즉, 거의 모든 렌더 스테이트가 동일한데 텍스쳐 하나 때문에 4번의 드로우 콜을 보내야 하는 것이다.

 

하지만, 아틀라스 텍스쳐를 사용한다면?

 

아틀라스 텍스쳐란, 하나의 이미지 파일에 위처럼 여러 오브젝트에 사용될 텍스쳐를 모아놓은 것이다.

여기서 UV값을 이용하여 필요한 만큼 잘라서 텍스쳐를 메쉬에 입히는 것이다.

 

이러한 이미지 파일로 텍스쳐를 생성한 뒤, 부위별로 UV만 매칭해준다면 서로 다른 메쉬이지만 같은 텍스쳐를 사용하는 셈이 되는 것이다. 즉, 눈, 머리, 상의, 하체에 대해 렌더 스테이트가 완전히 동일해지는 것이다.

 

이러한 경우엔 4개의 메쉬를 하나로 합쳐버리더라도 품질의 손상 없이 렌더링이 가능해진다.

(메쉬를 나눠놓는 이유가 대부분 다른 텍스쳐를 입히기 위함이다.)

 

아틀라스 이미지를 활용하면, 여러 메쉬를 하나로 합치는 것이 가능하고 드로우 콜을 1번으로 압축하는게 가능하게 된다는 것이다!

 

즉 배칭이란, 같은 머티리얼을 사용하는 여러 메쉬를 하나로 합쳐 한 번의 드로우 콜로 렌더링하는 방식이라는 것이다.

 

배칭은 스태틱 배칭, 다이나믹 배칭 두 가지 종류가 있다.

 

스태틱 배칭은 로딩 타임에 미리 매쉬들을 배칭하는 것이다. 주로 움직이지 않는 오브젝트(맵, 장식물등)에 대해 사용한다.

여러 메쉬 중 같은 머티리얼을 사용하는 메쉬를 하나로 묶어, 한 번의 드로우콜로 렌더링을 해결하게 해준다.

 

다이나믹 배칭은 런타임에 배칭을 진행하는 것이다. 주로 움직이는 대상에 대해 사용한다.

실시간으로 오브젝트를 하나로 합치는 연산을 진행하며, 드로우 콜을 줄인다고 한다.

 

하지만, 이러한 배칭에도 문제가 있다.

 

스태틱 배칭의 문제점

 

1. 메모리 사용량이 증가한다.

: 덩어리로 합친 메쉬를 추가적으로 VRAM (GPU 메모리)에 올려놓아야 하기 때문에 메모리 사용량이 증가한다.

 

2. 컬링을 제대로 적용할 수 없어진다.

: 메쉬가 나누어져 있을 땐, 화면 밖으로 벗어난 메쉬만 렌더링 하지 않는 것이 가능하지만 하나로 합쳐버리는 순간  화면을 벗어나는 부분만 렌더링 하지 않는 것이 불가능해진다.

 

다이나믹 배칭의 문제점

 

1. 연산량이 너무 많다.

: 실시간으로 연산하기엔 연산량이 너무 많아, 버텍스의 수가 많으면 오히려 드로우 콜을 줄임으로써 얻는 이득보다 배칭으로 얻는 손해가 더 커질 수 있다고 한다.

 

배칭이라는 기술이 여러모로 효율적으로 보이는 것 같으면서도, 상황에 따라 비효율적일 수 있기 때문에 신중하게 사용하는 것이 좋을 것 같다.