루키스 게임수학 정주행 3회차 - 장치 심화

2023. 6. 6. 17:35DirectX12

사실 이전에 "장치 초기화"라는 제목으로 글을 쓴 적이 있었는데 4가지 클래스를 정의한 적이 있었다. 근데 사실 코드를 따라 치면서 이해하지 못한 부분들이 있었고 그래서 그것들을 하나하나 짚고 넘어갈 것이다.

 

1. GraphicDevice

간단하게 설명하면 GPU에게 명령을 내리기 위한 인터페이스로 ID3D12Device와 IDXGIFactory를 멤버로 가지고 있다. 이제 이 두 자료형이 DX12에서 어떤 역할을 하는지 알아보자.

 

1) ID3D12Device

  1. 리소스 관리: GPU가 그림을 그리도록 명령을 하려면 무엇을 그려야 하는지 알려줘야 한다는 사실은 너무 자명하다. 즉, 이 얘기는 GPU가 무엇을 그려야 하는지에 대한 데이터를 어딘가에 저장해야 한다는 뜻이기도 하다. 바로 이 데이터가 "리소스"이며 문맥에 따라 메모리를 가리키기도 한다. 여기서 말하는 리소스 관리는 그 메모리를 관리한다는 것이다.
  2. 그래픽 명령: 당연하게도 무엇을 그려야 하는지 알았다면 그것을 그리게 될 것이다. 여기서 중요한 것은 "어떻게" 그리는 지를 아는 것이다. 여기서 ID3D12Device는 "어떻게" 그리는지를 GPU에게 명령한다. 어떻게 그리는지에 대한 자세한 내용은 그래픽스 파이프 라인을 배우면서 알게 될 것이다.
  3. 디스크립터 힙 관리: descriptor를 직역하면 서술자라는 뜻으로 서술자 힙이라는 것을 관리한다는 뜻이다. 디스크립터 힙은 리소스에 대한 정보를 담고 있는 메모리로 이걸 관리해서 리소스에 대한 정보를 관리하는 것을 의미한다.
  4. 자원 상태 및 동기화 관리: GPU가 그려야 할 리소스가 현재 어떤 상태인지 확인하고 병렬 작업 즉, 멀티 쓰레딩을 위해 CPU와 GPU사이의 동기화 작업을 시키고 충돌을 방지하는 역할을 제공한다.

 

2) IDXGIFactory

  1. 그래픽 어댑터 및 디스플레이 모드 관리: 우리가 컴퓨터를 맞춘다고 하면 흔히 GPU와 같은 그래픽 장치를 같이 맞추고는 하는데 이런 그래픽 장치를 그래픽 어댑터라고 한다. 디스플레이 모드란 어떤 방식으로 모니터 화면에 그림을 그릴지를 말하는 것이다. 즉, 그래픽 어댑터 및 디스플레이 모드 관리란 컴퓨터에서 현재 사용하고 있는 그래픽 장치에 대한 정보를 관리하고 해당 장치가 어떤 방식으로 모니터 화면에 그림을 그려줄지에 대한 정보를 제공하고 세팅하게 관리한다는 것이다.
  2. 그래픽 리소스 생성: 사실 chatgpt에 나온 결과를 열거하고 있는 것이긴 한데 개인적으로 스왑 체인 생성이라는 표현이 더 나을 것 같다는 생각이 들기는 한다. CreateSwapChain이라는 함수를 사용해서 생성하는데 스왑 체인에 대한 설명은 이후에 서술하겠다.
  3. 그래픽 어댑터 정보 제공: 1번과 겹치는 내용이다. 말 그대로 우리가 사용하는 GPU와 같은 그래픽 장치에 대한 정보를 제공한다는 뜻이다. 다시 말하지만 chatgpt에 나온 내용을 그대로 열거하는 중이다.

클래스 GraphicDevice의 Init에 대한 요약

 

 

2. CommandQueue

우리가 GPU에게 명령을 내릴 때, 명령이 들어올 때마다 바로바로 GPU에게 명령을 내리는 것이 아니라 명령을 어느 정도 큐에 모았다가 한꺼번에 GPU에게 명령을 전달해서 처리하는 기능을 담당한다. 이를 위해 DirectX12는 내부적으로 3가지를 제공하는데 그것이 ID3D12CommandQueue, ID3D12GraphicsCommandList, ID3D12CommandAllocator이다.

 

1) ID3D12CommandQueue

DirectX12 내에 존재하는 커맨드 큐에 대한 인터페이스로 ID3D12GraphicsCommandList에서 명령을 받은 것을 처리하는 역할을 한다. ID3D12Device에서 CreateCommandQueue라는 함수를 호출시키면 생성할 수 있고 ID3D12CommandList의 주소를 인자로 받는 ExecuteCommandLists를 통해 리스트로부터 큐로 명령을 받아서 실행할 수 있다.

 

2) ID3D12GraphicsCommandList

DirectX12 내에 존재하는 커맨드 리스트에 대한 인터페이스로 ID3D12CommandQueue가 요청을 하면 커맨드 큐에 명령을 전달하는 역할을 한다. ID3D12Device에서 CreateCommandList라는 함수를 호출시키면 생성할 수 있고 커맨드 큐에게 명령을 전달하고 실행시키는 ExecuteCommandLists의 인자로 들어간다. 커맨드 큐에 전달하는 명령에는 그리기, 복사, 상태 전이 등이 있으며 앞으로 배울 그래픽스 파이프 라인에 나오는 내용에 따라 이러한 명령들이 수행되는 것이다.

 

3) ID3D12CommandAllocator

커맨드 리스트에 있는 커맨드들을 저장할 메모리를 관리한다. 즉, 커맨드 리스트에 있는 커맨드들은 여기에서 할당해주는 메모리에 저장된다는 뜻이다. 실제로 CreateCommandList라는 함수를 보면 ID3D12CommandListAllocator를 인자로 받는 모습을 볼 수 있다. ExecuteCommandLists를 실행해서 커맨드 리스트를 제출해서 커맨드 큐에 전달을 하면 그 커맨드 큐에 있는 명령을 실행할 때, 맨 처음 CreateCommandList를 실행시킬 때 인자로 넘겼던 ID3D12CommandListAllocator를 참조하게 된다. 그래서 최종적으로 커맨드 리스트와 알로케이터가 서로 하나씩 연관되어 있는 구조가 되는 것이다.

 

4) ID3D12Fence

GPU와 CPU의 동기화를 하는 역할을 한다. 보통 이런 상황에서 동기화라는 것은 작업의 상태 혹은 진척 속도같은 것을 비슷하게 맞춰주는 것을 의미한다. 그리고 이것을 위해 CPU 혹은 GPU 측에서 다른 나머지 한 쪽이 작업이 끝날 때까지 기다려야 할 것이다. Fence라는 단어가 울타리를 뜻하는 단어인데 여기서 말하는 울타리란 다른 한 쪽의 작업이 끝날 때까지 작업이 진행되지 않고 기다리도록 울타리를 친다는 뜻에서 Fence라고 한 것이다... 라고 강의해서 말했는데 사실인지는 잘 모르겠다. 어쨋든 Fence가 동기화를 하는 역할을 한다고 한다.

클래스 CommandQueue의 Init에 대한 요약

 

 

3. DescriptorHeap

직역하면 서술자 힙이라는 뜻이고 화면에 그려질 리소스들을 서술한다고 해서 서술자 힙이라고 이름을 지은 것 같다. 실제로 우리가 GPU를 통해 그릴 리소스에 대한 정보들이 힙이라는 자료구조의 형태로 이곳에 저장된다.

 

1) ID3D12DescriptorHeap

우선 디스크립터라고 하면 그래픽 리소스에 대한 데이터를 의미하는데 이 뒤에 Heap이라는 단어가 붙으면 이런 디스크립터라는 것을 저장하는 Heap을 의미한다고 이해하면 된다. Heap이라는 자료구조로 저장이되며 인덱스를 통한 접근을 제공하는 방식이다. 사실 디스크립터 힙의 종류가 RTV, DSV, CBV 등 여러가지가 있는데 DX11은 이런 것들이 따로 존재했지만 DX12로 넘어오면서 이것들을 ID3D12DescriptorHeap으로 한꺼번에 묶어서 제공하고 있다. CreateDescriptorHeap이라는 함수를 호출해서 생성할 수 있으며 ID3D12Device를 통해 호출할 수 있다.

 

2) RTV(Render Target View)

여기서 말하는 Render Target은 백 버퍼에 그려지는 것을 의미하는데 RTV는 백 버퍼를 가리키는 포인터라고 보면 된다. RTV는 ID3D12Device의 CreateRenderTargetView라는 함수를 호출해서 생성할 수 있는데 이 과정에서 인자로 받는 ID3D12Resource는 Render Target을 의미한다. 지금 생성하는 ID3D12DescriptorHeap은 이러한 RTV를 용도로 생성하고 있는 것이다.

 

3) D3D12_CPU_DESCRIPTOR_HANDLE

chatgpt에 따르면 D3D12_CPU_DESCRIPTOR_HANDLE은 DirectX 12에서 CPU에서 디스크립터 힙(Descriptor Heap) 내의 디스크립터를 가리키는 핸들(Handle)이라고 나와있다. RTV 역시 디스크립터의 한 종류로써 첨부된 코드를 보면 초기화를 시키는 과정에서 D3D12_CPU_DESCRIPTOR_HANDLE 배열에 값을 넘기는 것을 볼 수 있다. 이게 RTV가 가리킬 주소를 등록하는 과정으로 보면 된다. GetCPUDescriptorHandleForHeapStart라는 함수 호출을 통해서 디스크립터 힙의 시작 주소를 받아서 CPU가 이러한 디스크립터 힙에 접근할 수 있도록 만드는 것이다.

클래스 DescriptorHeap의 Init에 대한 요약

 

 

4. SwapChain

https://dafher-diary.tistory.com/54

 

루키스 게임수학 정주행 2회차 - 장치 초기화

1. CPU vs GPU 우리가 대부분 사용하는 컴퓨터에는 CPU와 GPU라는 부품이 거의 대부분 기본적으로 장착되어 있을 것이다. 오늘 할 것은 엔진을 만드는 데 있어서 GPU에게 효율적으로 일을 시키는 일인

dafher-diary.tistory.com

우선 스왑 체인을 공부하기 전에 더블 버퍼링이 무엇이며 이것이 왜 필요한지 알아볼 필요가 있다. 위의 링크의 2번 내용이 해당 내용을 다루고 있으니 참고하면 좋다. 스왑 체인은 말 그대로 체인을 스왑한다는 뜻이다. 체인이 뭐길래 그것을 서로 바꾼다는 걸까? 여기서 체인이라고 하면 더블 버퍼링의 내용에서 나오는 어떤 리소스 공간을 가리키는 화살표를 생각하면 된다. 그럼 더블 버퍼링을 위해 연산을 하는 쪽과 보여주는 쪽을 서로 바꾼다는 뜻임을 알 수 있을 것이다.

 

1) IDXGISwapChain

스왑 체인은 방금 얘기한 더블 버퍼링 방식으로 그래픽을 화면에 출력하는 역할을 하는 개체이며 IDXGISwapChain은 이러한 스왑 체인의 인터페이스의 역할을 하는 클래스이다. IDXGIFactory의 CreateSwapChain이라는 함수를 호출해서 생성할 수 있고 인자로 커맨드 큐와 DXGI_SWAP_CHAIN_DESC라는 것을 받는다. 커맨드 큐를 받는 이유는 SwapChain이 그래픽 리소스를 그리기 위해서 커맨드 큐에 명령을 전달해야 하기 때문이며 DXGI_SWAP_CHAIN_DESC는 스왑 체인의 각종 정보를 저장하는 구조체로 여기서 스왑 체인의 속성을 세팅해야 하기 때문이다.

 

2) ID3D12Resource

화면에 그려질 그래픽 데이터를 나타내는 인터페이스이며 IDXGISwapChain의 GetBuffer를 호출해서 가져올 수 있다. 스왑 체인이 2개의 버퍼를 통해서 이후에 나올 RTV를 다루면서 다시 보게 될 개념인데 여기서 더블 버퍼링에 대해서 다시 짚을 필요가 있다. 이전에 작성한 포스팅에서 화면에 보여질 그림을 백 버퍼에 그린다는 이야기를 한 적이 있는데 기억이 나는가? 그 백 버퍼를 Render Target이라고 이야기하고 DX12는 그것에 대한 인터페이스를 ID3D12Resource로 제공하고 있다.

클래스 SwapChain의 Init에 대한 요약

 

초기화 소스 코드.zip
0.04MB