Programming/D3D12

[책공부] 박스 그리기 예제

Dorasima 2023. 12. 27. 22:21

(사실 이해했는지 모르겠다. 어렴풋이 느껴질 뿐... 그래도 그 느낌을 정리한다.)

(챕터가 넘어갈 수록, 글쓴이가 점점 강해지니 좀 읽을만한 정보는 후반 챕터에 있을 것이다. ㄹㅇㅋㅋ)

다음 글

 

1. 박스 예제(까지)가 연습시켜주는 기술

 

1_1 초기화 단계

- 창(window) 생성

- 어뎁터 (D3DDevice) 생성

- GPU와 CPU 동기화를 위한 Fence 생성

- GPU와 CPU의 [데이터 전송을 위한 Descriptor View]의 Handle Size 초기화

    (아마 요걸로 데이터 블록(아마도 테이블?)을 점프하면서 데이터를 읽을 것 같다.)

- Command Queue, Command Allocator, Command List 생성

    (멀티 스레딩을 위해 새로 바뀐 GPU에게 렌더링 동작을 요청하는 방법)

- Swap Chain에서 Render Target 얻기

- Render Target 의 정보를 GPU와 연결할 Descriptor (View)

- Render Target 의 정보를 GPU와 연결할 Descriptor 를 또 (GUID로 구분하는) 공간인 Descriptor  heap에 연결

- GPU에서 사용할 Depth - Stencil의 정보를 가지는  Descriptor (View)

- GPU에서 사용할 Depth - Stencil의 정보를 가지는  Descriptor 를 또 (GUID로 구분하는) 공간인 Descriptor  heap에 연결

- Viewport 설정

(이것 말고 아직 초기화를 했지만, 아직 제대로 사용하지 않아서 언급하지 않은 많은 요소들이 있다.)

 

1_2. 박스 정보 + 그걸 GPU에 넘기기

- 박스의 화면상의 위치 (+ 기타 로직) 를 정하는 GPU에서 사용하는 Vertex Shader

- 박스의 점을 진짜로 찍는(+ 기타 로직) GPU에서 사용하는 Pixel Shader

- 박스 도형 자체를 정의하는 Vertex Buffer와 Index Buffer

- Shader에서 사용하는 다양한 데이터인 Constant Buffer

(이게 이것들을 GPU에 넘겨주는 것)

(CPU 데이터를 GPU가 받기 위해서는, 그 데이터가 뭔지, 어떤식으로 들어올지 알려줘야 한다.)

- Constant Buffer의 값은 CPU에서 갱신한다. 그래서   Descriptor  (View)로 넘겨줘야하니 Descriptor  를 만들고, 또 그 Descriptor 를 Heap에 연결한다.

- 이 Descriptor Heap을 또 Root Signature라는 것에 정보를 넣고 등록을 해줘야 하는데, 해당 예제 에서는 (D3D12_ROOT_DESCRIPTOR_TABLE으로) Descriptor Table의 형태로 만든 다음, Constant Buffer Descriptor가 있는 Descriptor Heap을 등록해준다.

- GPU는 Shader로, Shader는 기본적으로 Vertex 정보로 화면에 점을 찍는다. Vertex의 정보는 ID3D12Resource 형태로 GPU에 올라가는데, GPU가 그걸 잘 해석하기 위한, Layout 정보도 알려줘야 한다.  (D3D12_INPUT_ELEMENT_DESC)

(박스 정보 이외에도, 어떻게 그릴건지 자체에 대한 속성도 있지만 아직 안배워서 적지 않는다.)

- D3D12에서는 PSO( pipeline state object )라고, 어떠한 상태나 데이터들을 한데 묶어서 오브젝트를 만든 다음에, 그걸 세팅을 한방에 해놓고 렌더링을 하는 방법을 한다.( PIPELINE_STATE_DESC )

-  근데 PIPELINE_STATE_DESC에 들어가는 정보가 있고, 그렇지 않은 정보가 있는데 일단 여기 예제에서

- PSO 들어가는 정보를 구조체에 넣어주고 생성을 해놓는다.

- 들어가는 정보는 Vertex Layout, 컴파일 된 셰이더(Vertex, Pixel), Constant Buffer View가 들어가 있는 View heap이 등록된 Table로 된 Root Signature, 렌더링 state (Rasterizer State, Blend State, DepthStencil State, SampleMask, Multi-Sampling 등등 - 이건 아직 안써봐서 기본 상태이다.), 각종 렌더링 포멧 (Topology Type, 멀티 샘플링 등), 버퍼 포멧 (렌더 타겟 포멧, 깊이 스텐실 포멧) 이다.

 

1_3. 박스 그리기 단계

- 1_1에서 지나가듯 언급했는데, D3D12에서는 GPU에게 Rendering 요청하는 방식을 Command Queue에 Command List를 제출하는 방식으로 바뀌었다.

- 그래서 렌더링 로직 초기에, Command List와 Command Allocator를 초기화 한다. Command List는 CPU가 GPU에게 요청하는 각종 Rendering 명령을 저장할 수 있는 공간이고, Command Allocator는 그 Command List에 CPU가 접근 할 수 있게 해주는 역할을 한다.

- 하지만 Command List와 Command Allocator를 초기화 하려면, 초기화 하려는 친구들이 Command Queue에서 작업을 모두 마친 상태여야 한다. (예제에서는 Flush를 따로 만들어서, 매 Draw 마다 걸어준다.)

- (어째서인지 위에서 언급이 안되었는데), D3D12는 멀티 스레드를 위해서 설계가 되었고, 그걸 위해서 Command 뭐시기로 렌더명령을 내린다고 했다.  Command List는 여러개 만들 수 있고, (Command Queue도 여러개 만들 수 있다.) 올라간 Command List가 언제 처리할지 모른다. 어떤 Resource에 접근하는 Descriptor가 여러개가 될 수 있는 것이다.

- 그래서 리소스( ID3D12Resource)에 대해서 리소스 장벽(ResourceBarrier)를 만들어서 프로그래머가 안정성을 관리해주도록 한다..... (근데 나의 경우는 실력이 떨어져서 직접해주는게 더 느려질 수도 있겠다는 걱정이 먼저 든다. ) 

- 무튼 GPU에서 Render Target에 그림을 그려야 하니, Barrier Transition 을 PRESENT 에서 RENDER_TARGET으로 바꿔주는 명령을 등록한다.

-  Back Buffer와 Depth-Stencil Buffer를 Clear 해주고, OM(Out-Merge) 단계에 세팅해주는 명령을 등록한다.

- Constant Buffer 값을 넘겨주기 위해 View Heap을 가져온다. 그에 대한 정보(여러개 있으면 배열로 만들어서) 를 GPU에 세팅하는 명령을 등록한다.

- Constant Buffer의 정보를 등록했던 Table로 만들었던, RootSignature 도 GPU에 등록을 하고, 그 Table에다가 View Heap 을 등록하는 명령을 등록한다. (여러개면 번호당 하나씩)

- Vertex Buffer와 Index Buffer를 가리키는 View를 IA (Input - Assembly) 단계에 세팅하는 명령을 등록한다.

- D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST 로 PrimitiveTopology로 하는 명령을 등록한다.

- 이제 드디어 DrawIndexedInstanced를 이용해서 박스를 그리는 명령을 등록한다.

- 박스 다 그린 다음은 GPU에서 Render Target을 읽어서 화면에 그림을 그려야 하니 RENDER_TARGET에서 PRESENT로 상태를 바꾸는 명령을 등록한다.

- 이제 등록한 명령을 (Command List) Command Queue에 등록 한다. (ExecuteCommandLists)

- 그리고 IDXGISwapChain::Present 를 걸어주고, Back Buffer의 Index를 교체한다.

 

2. 아직 잘 모르는 것

- Resource Barrier 에 관한 것이다. 나중에 어디다가 질문 올려야 겠다.

- 아래는 질문 미리 복사해놓기

더보기

질문 이전에 제가 이해하는... 느끼는부분 설명 :

1. 함수 모양을 보고 유추했을 때, 이전 단계랑 이후 단계가 파라미터로 들어가는걸 보아, 이전 상태가 만족이 되어야 Transition이 올바르게 일어나는 것 같고

2. MSDN을 봤을 때,

2_1. 프로그래머가 앱 단에서 어떤 반응형 장벽(?) 처럼 작동하는 Barrier Transition을, Command List에 넣어주면, GPU 안에서 그것이 작동 되어서 Wait을 거는 느낌인 것 같고

2_2. ExecuteCommandLists() 설명에도 다른 스레드여도 실행 시간에 따른 순서를 맞춰주고, 런타임 도중에 상태 변환 유효성을 검사해준다고 하는데

 

질문 : 만약 같은 Resource를 동시에 막 접근하는 List로 가득찬 Queue 여러개가 동시에(?) GPU에 넘어가도 Resource Barrier가 잘 작동하나요? 결국 GPU가 어떻게 내부적으로 작동하는지 알아야 하는건가...