루키스 게임수학 정주행 5회차 - 장치 초기화 최종 정리

2023. 7. 6. 18:07DirectX12

1. 시작하기 전에...

이전 포스팅부터 말했지만 스스로 리마인딩을 시키기 위해서 다시 한 번 글로 적겠다. 절대 완벽주의로 진도를 나가면 안된다. DirectX 12를 배운다는 것은 CPU가 GPU에게 그래픽 명령을 내리는 흐름을 배운다는 것을 알아야 하며 전체 흐름을 보는데 방해가 되지 않도록 나무가 아닌 숲을 보고자 노력해야 한다. 그래서 이번 포스팅부터는 각 함수, 클래스들의 자세한 사항을 적지 않을 것이다. 각 코드가 뭘 의미하는지 이해가 안가더라도 전체적인 흐름을 간단하게나마 이해했다고 판단이 들면 그냥 넘어갈 것이다. 첫 정주행은 이렇게 할 것이다. 두 번째 정주행은 언제가 될 지 모르겠지만 말이다.

 

 

2. 결국 장치 초기화란?

그래픽스 API로 엔진을 만든다는 것은 컴퓨터에게 그래픽과 관련된 처리에 대해 명령을 내리는 것을 의미한다. 근데 잘 생각해보자. 지금까지 우리가 짠 코드는 전부 CPU가 처리해주지 않았는가? 실제로 우리가 명령을 내리면 컴퓨터의 구조상 CPU에게 명령이 전달되며 원래는 그래픽과 관련된 처리도 CPU에게 전달이 될 것이다. 근데 아래의 글을 보면 알겠지만 CPU의 구조상 각 픽셀에 대한 값을 계산하기에는 비효율적이어서 이는 CPU가 처리하지 않는다.

 

(이 글의 1, 2를 이해하고 와야 지금 이 포스팅을 읽는 것이 도움이 된다. 3은 여기서 정리할테니 건너뛰자.)

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

 

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

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

dafher-diary.tistory.com

 

결국 장치 초기화라는 것은 CPU가 GPU에게 일을 시키기 위해서 몇몇 클래스들을 객체로 생성하고 필요한 설정들을 세팅하는 과정을 의미한다. 여기서 세팅이란 어떤 것을 그리고 어떻게 그리고 어디에 그리는지 등을 설정하는 것이라고 생각하면 된다. 결국 결론은 우리가 배울 것이 아래의 이미지와 다를 것이 없다는 뜻이다.

화질 극혐...;;

 

 

3. GPU의 인터페이스 - ID3D12Device, IDXGIFactory

ChatGPT에 저것들이 뭐냐고 물으면 다음과 같은 대답이 돌아온다.

나: ID3D12Device가 뭐야?

ChatGPT: ID3D12Device는 DirectX 12 그래픽 API에서 가장 중요한 인터페이스 중 하나입니다. 이 인터페이스는 Direct3D 12 장치를 나타내며, 그래픽 리소스를 관리하고 GPU에 그래픽 명령을 전달하는 등의 역할을 수행합니다.

ID3D12Device를 사용하여 다음과 같은 작업을 수행할 수 있습니다:

1. 그래픽 리소스 생성 및 관리: ID3D12Device는 버퍼, 텍스처, 렌더 타겟 등의 그래픽 리소스를 생성하고 관리하는 기능을 제공합니다. 리소스의 메모리 할당, 상태 전이, 동기화 등을 관리할 수 있습니다.

2. 그래픽 명령 생성 및 실행: ID3D12Device는 ID3D12CommandQueue와 함께 사용하여 그래픽 명령 리스트(ID3D12GraphicsCommandList)를 생성하고 실행할 수 있습니다. 명령 리스트에 그래픽 명령을 기록하고, GPU에 전달하여 실행할 수 있습니다.

3. 루트 시그니처 관리: ID3D12Device는 루트 시그니처(ID3D12RootSignature)를 생성하고 관리하는 기능을 제공합니다. 루트 시그니처는 그래픽 파이프라인의 상태와 리소스 바인딩을 정의하는 중요한 요소입니다.

4. 리소스 및 명령어 큐 동기화: ID3D12Device를 사용하여 리소스와 명령어 큐 간의 동기화를 수행할 수 있습니다. 이를 통해 리소스의 사용 및 해제, 명령어의 실행 순서 등을 제어할 수 있습니다.

ID3D12Device는 DirectX 12 그래픽 애플리케이션에서 핵심적인 역할을 담당하는 인터페이스이며, 그래픽 리소스 및 명령어의 생성, 관리, 실행 등을 수행하는데 필요한 기능을 제공합니다. (뭔지 모르겠당 ㅎㅎ)


나: DXGI가 뭐야?

ChatGPT: DXGI는 DirectX Graphics Infrastructure의 약자로, DirectX에서 그래픽 애플리케이션과 하드웨어 간의 인터페이스를 제공하는 라이브러리입니다. DXGI는 주로 그래픽 어댑터, 디스플레이 모드, 스왑 체인 등과 관련된 기능을 제공하며, 다양한 그래픽 애플리케이션을 개발할 때 사용됩니다.

DXGI의 기능은 다음과 같습니다:

1. 그래픽 어댑터 관리: DXGI는 시스템에 설치된 그래픽 어댑터(그래픽 카드) 정보를 가져올 수 있고, 어댑터의 속성 및 지원 기능을 확인할 수 있습니다.

2. 디스플레이 모드 관리: DXGI는 어댑터에서 지원하는 디스플레이 모드(해상도, 주사율 등)를 확인하고, 애플리케이션에 적합한 디스플레이 모드를 선택할 수 있습니다.

3. 스왑 체인 생성: DXGI는 스왑 체인(Swap Chain)을 생성하여 전면 버퍼(Front Buffer)와 후면 버퍼(Back Buffer)를 교환하는 메커니즘을 제공합니다. 스왑 체인은 화면에 그래픽을 표시하는 데 사용되며, 부드러운 애니메이션 및 화면 갱신을 지원합니다.

4. DXGI 객체 생성 및 관리: DXGI는 IDXGIFactory를 통해 DXGI 객체를 생성하고 관리합니다. IDXGIFactory는 DXGI 객체 생성 및 열거, 디바이스 생성 등의 기능을 제공합니다.

DXGI는 DirectX 그래픽 애플리케이션에서 그래픽 디바이스 및 디스플레이와 관련된 작업을 수행하는 데 필요한 인터페이스와 기능을 제공합니다.

 

이걸 보고나서 다음과 같은 생각이 들었다면 나와 같은 정상인(?)이라고 봐도 무방하다.

이 뭔 개소리야?

 

그렇다... 처음보면 이해가 안된다. 하지만 쉽게 생각하면 쉽게 풀린다. 어렵게 얘기해서 저렇게 느끼는거지 전체적인 내용을 알면 전혀 쫄 필요가 없다. 그럼 ID3D12Device와 IDXGIFactory는 무엇일까?

 

"ID3D12Device와 IDXGIFactory는 GPU에 접근하기 위한 인터페이스이다."

 

그저 GPU를 통해 내리는 명령이 다르고 수행하는 역할이 다를 뿐이다. 둘 모두한테 " I "라는 알파벳이 붙어있지 않은가? 그저 Interface의 약자일 뿐이며 GPU에게 명령하기 위한 통로, 연락책같은 것이라는 거다. 놀랍게도 전체적인 흐름을 이해하는 것에 있어서는 이렇게 한 문장으로 요약하고 넘어가도 아무런 문제가 없다. 이미지로 비유해서 표현하면 아래와 같다.

그래픽 처리를 주력으로 하는 주식회사 GPU의 역할은 대기업 CPU의 하청업체이다.

 

이제 각 부서에서 어떤 역할을 하고 어떤 팀 혹은 과가 존재하는지 알아보도록 하자.

 

4. ID3D12Device

인프런의 Rookiss님의 강의의 장치 초기화 예제 코드를 보면 커맨드 큐, 디스크립터 힙이라는 것이 있다. 커맨드 큐에 대해서 먼저 알아보자면 GPU에게 명령을 담아놓는 공간이라고 생각하면 된다. CPU가 GPU에게 명령을 전달할 때, 명령 하나 하나를 일일히 전달하는 것이 아니라 한꺼번에 묶어서 전달하는 것을 의미한다. 아래의 그림을 보자.

 

위 이미지는 다음 순서로 진행된다.

  1. CommandQueue, CommandList, CommandAllocator를 생성한다.
  2. CommandList를 CommandAllocator와 연결시킨다.
  3. CommandList에 명령을 추가한다. 이러면 할당자가 자동으로 명령을 메모리에 할당한다.
  4. CommandAllocator가 할당해준 명령들의 리스트를 CommandQueue에 적재시킨다.
  5. GPU가 CommandQueue에서 알아서 명령을 빼내서 수행한다.

 

편한 이해를 위해 그림으로 다시 보자.

CommandQueue, CommandList, CommandAllocator를 생성한다.

대충 이렇게 있다고 가정하자.

 

CommandList를 CommandAllocator와 연결시킨다.

 

CommandList에 명령을 추가한다. 이러면 할당자가 자동으로 명령을 메모리에 할당한다.

 

CommandAllocator가 할당해준 명령들의 리스트를 CommandQueue에 적재시킨다.

 

GPU가 CommandQueue에서 알아서 명령을 빼내서 수행한다.

이런 과정에 따라서 최종적으로 이런 이미지가 나오는 것이다.

 

주식회사 GPU는 대기업 CPU의 하청업체로써 위에서 서술했던 일들을 처리하기 위해 CommandQueue라는 팀을 만들었고 3개의 과를 추가했다. 이제 주식회사 GPU의 부서구조는 아래와 같이 변경된 것이다. 이는 CommandQueue 팀에 있는 것들이 실제로 ID3D12Device라는 객체의 멤버 함수로부터 생성되는 것을 반영한 것이다.

 

5. IDXGIFactory

인프런의 Rookiss님의 강의의 장치 초기화 예제 코드를 보면 IDXGIFactory에서 더블 버퍼링을 수행하는 객체를 생성하는 모습을 볼 수 있다. 그 객체는 IDXGISwapChain이라는 객체이며 이 녀석이 더블 버퍼링 작업을 수행한다. 더블 버퍼링이 무엇인지에 대한 설명은 다른 글에서 다뤘으니 생략하고 더블 버퍼링에 필요한 것들을 다뤄볼 것이다.

 

우선 신설된 팀들을 아래의 이미지로 소개하겠다.

더블 버퍼링 업무를 수행할 새로운 부서들이다.

 

Resource와 SwapChain이라는 팀이 신설되었다. 이는 SwapChain은 IDXGIFactory에서 생성되며 ID3D12Resource와 ID3D12DescriptorHeap은 ID3D12Device에서 생성되는 것을 반영한 것이다. 아까 SwapChain은 더블 버퍼링을 한다고 했는데 그럼 Resource는 뭘 하는 녀석일까?

 

아래의 링크에 따르면 GPU가 일을 하려면 셰이더라는 프로그램이 있어야 하고 해당 프로그램으로 넘겨주는 데이터를 리소스라고 한다는 내용이 아래의 링크에 나와있다. (정리가 아주 깔끔하게 잘 되어 있다.)

https://ssinyoung.tistory.com/37?category=810267 

 

DirectX 12 장치 초기화 이해하기 (5) - part 1

[ DirectX 12 장치 초기화 단계 ] 1 단계 Device(그래픽 디바이스) 생성. 2 단계 CommandQueue와 CommandList 생성. 3 단계 SwapChain 생성. 4 단계 FenceObject 생성. 5 단계 렌더타겟(RenderTarget)과 깊이/스텐실(Depth/Stenci

ssinyoung.tistory.com

 

그럼 셰이더는 무엇일까? 셰이더는 각 픽셀의 색을 어떻게 해야 하는지를 GPU에게 명령을 내리는 프로그램이다. 우리가 CPU에게 명령을 내리기 위해서 C++로 소스 코드를 작성하고 실행을 시켜서 명령을 내리듯이 여기서도 GPU가 알아들을 수 있는 언어로 코드를 작성하고 실행시켜서 GPU에게 명령을 내리는 것이다. 물론 다른 방법으로도 GPU에게 명령을 내릴 방법이 찾으면 나오겠지만 어쨋든 위의 링크에 따르면 GPU를 통해 모니터에 그림을 그리기 위해 셰이더에 넣는 데이터를 리소스라고 한다.

 

물론 문맥에 따라서 리소스를 "화면에 그림을 그리는데 필요한 데이터" 혹은 "데이터가 저장된 메모리 공간"으로 다양하게 해석될 수 있지만 여기서는 "GPU라는 하청업체가 모니터에 그림을 그리는 일을 처리하기 위해 필요한 도구" 정도로 그 뜻을 통일할 것이다. 즉, 그림으로 다시 표현한다면 아래와 같은 그림이 나오는 것이다.

일해라! 노예야!

 

 

6. ID3D12DescriptorHeap

DX11에는 View라는 개념이 있었다. 리소스를 어떻게 볼 것인가? 라는 맥락의 View라고 생각하면 될 것이다. 이러한 View는 상당히 다양한 버전으로 제공이 되었었다. RTV, DSV, UAV 등등의 클래스가 각각 따로 제공이 되었고 이것들을 생성하는 함수가 각 클래스마다 따로 존재했다. 그런데 DX12로 넘어오면서 이것이 DescriptorHeap이라는 개념으로 통합되게 되었다. 실제로 소스 코드를 보면 아래와 같이 매개 변수를 받아서 그 값에 따라 생성하는 것을 볼 수 있다.

// 서술자 힙을 서술한다는 뜻으로 뒤에 DESC가 붙었다.
D3D12_DESCRIPTOR_HEAP_DESC rtvDesc;
rtvDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvDesc.NumDescriptors = SWAP_CHAIN_BUFFER_COUNT;
rtvDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
rtvDesc.NodeMask = 0;

// D3D12_DESCRIPTOR_HEAP_DESC에 들어가는 값에 따라 생성되는 뷰의 종류가 달라진다.
// 지금은 RTV(Render Target View)가 생성되도록 만들어졌다.
_device->CreateDescriptorHeap(&rtvDesc, IID_PPV_ARGS(&_rtvHeap));

 

Rookiss님은 강의에서 DescriptorHeap을 기안서라고 예시를 들었다. 즉, 앞서 들었던 예시에서 신설되었던 Resource라는 팀의 ID3D12DescriptorHeap이라는 과가 이 설명서를 작성하는 과인 것이다. CPU가 GPU에게 하청을 줄 때, "이 리소스라는 도구를 이렇게 사용하세요" 라고 설명하는 것이다. 그러니까 그림으로 간략하게 설명하면 이렇게 되는 것이다.

사실 아직도 갈 길이 멀다.

 

7. 정리를 마치며...

여기에서 얼마나 오래 표류를 했는지 모르겠다. 꽤 오래 표류한 것 같은데 솔직히 내용이 어려워서 오래 걸린 것도 있었지만 사실 그것보다 훨씬 더 진도를 나가지 못하게 했던 주범이 있었으니... 바로 이거 때문이다.

 

이거 하느라 진도 안나갔다... ㅎㅎ 그래서 벌을 받는 것일까...? 취업이 잘 안되서 도피성으로 대학원 진학을 선택하게 되었다. (젠장... ㅠㅠ) 그리고 후배랑 하던 프로젝트도 탈퇴하게 되었다. 대학원을 간다니까 내가 불쌍해진 것일까? 어쨋든 프로젝트와 별개로 포스팅은 계속 올릴 생각이긴 한데 다음 글은 무슨 글을 주제로 쓰게 될지 모르겠다. 교수님이 AI 하신다던데 한 때 정주행했었던 이상엽의 수학 강의를 정주행하게 될 것 같다는 생각은 드는데 잘은 모르겠다.

 

확실한 것은 DX12의 장치 초기화와 관련된 글은 이것으로 마지막이라는 것이다. (X같은 거...)