기록공간

[DirectX 12] 기본지식 - 명령 대기열(큐)과 명령 목록(리스트) 본문

DirectX/기초

[DirectX 12] 기본지식 - 명령 대기열(큐)과 명령 목록(리스트)

입코딩 2020. 2. 12. 19:02
반응형

GPU에는 명령 대기열(Command queue)이 하나 있다. CPU는 그리기 명령들이 담긴 명령 목록(Command list)을 Direct3D API를 통해서 그 대기열에 제출한다. 여기서 중요한 점은, 일단의 명령들을 명령 대기열에 제출했다고 해도, 그 명령들을 GPU가 즉시 실행하는 것은 아니라는 점이다. 명령들은 GPU가 처리할 준비가 되어야 비로소 실행되기 시작한다. 즉, GPU가 이전에 제출된 명령들을 처리하느라 바쁘게 돌아가는 동안 명령들은 그냥 대기열에 남아 있다.

 

명령 대기열이 비면 GPU는 할 일이 없으므로 그냥 놀게 된다. 반대로, 대기열이 꽉 차면 GPU가 명령들을 처리해서 대기열에 자리가 생길 때까지 CPU가 놀게 된다.  상황 모두 바람직하지 않다.최적의 성능을 얻으려면 최대한 둘 다 바쁘게 돌아가게 만들어야 한다. 

 

명령 대기열


GPU 명령을 실행하기 위해서는 명령 대기열을 생성해야 한다. 명령 대기열을 대표하는 인터페이스는 ID3D12CommandQueue이다. 이 인터페이스를 생성 하려면 대기열을 서술하는 D3D12_COMMAND_QUEUE_DESC 구조체를 채운 후 CreateCommandQueue를 호출해야 한다. 

 

이 인터페이스의 주요 메서드 중 하나는 명령 목록에 있는 명령들을 대기열에 추가하는 ExecuteCommandLists 메서드이다. 

 

명령 목록들은 배열의 첫 원소부터 차례로 실행된다.

 

명령 목록을 대표하는 인터페이스는 ID3D12CommandList이다. 그러나 실제 그래픽 작업을 위한 명령 목록은 이 인터페이스를 상속하는 ID3D12GraphicsCommandList라는 인터페이스로 대표된다. ID3D12GraphicsCommandList 인터페이스에는 명령들을 명령 목록에 추가하는 여러 메서드가 있다. 

// mCommandList는 ID3D12CommandList의 포인터
mCommandList->RSSetViewports(1, &mScreenViewport);
mCommandList->ClearRenderTargetView(mBackBufferView,
Colors::LightSteelBlue, 0, nullptr);
mCommandList->DrawIndexedInstanced(36, 1, 0, 0, 0);

이 코드는 뷰포트를 설정하고, 렌더 타겟 뷰를 지우고, 그리기 호출을 실행하는 명령들을 추가한다.

 

이 메서드들의 이름을 보면 왠지 명령들이 즉시 실행될 것 같지만, 실제로는 그렇지 않다. 그냥 명령들을 명령 리스트에 기록(혹은 추가)하기만 한다. 나중에 ExecuteCommandLists를 호출해야 비로소 명령들이 명령 큐에 추가되며, 그러면 GPU가 그 명령들을 뽑아서 실행한다. 

 

명령들을 명령 목록에 다 추가했으면, Close 메서드를 호출해서 명령들의 기록이 끝났음을 Direct3D에 알려줘야 한다.

 

// 명령 목록을 닫는다(명령 기록이 끝났음을 알린다)
mCommandList->Close();

ExecuteCommandLists로 명령 리스트를 제출하기 전에 반드시 Close를 이용해서 명령 리스트를 닫아 주어야 한다.

 

명령 리스트에는 ID3D12CommandAllocator 형식의 메모리 할당자가 하나 연관된다. 명령 리스트에 추가된 명령들은 이 할당자의 메모리에 저장된다. ExecuteCommandLists로 명령 목록을 실행(혹은 제출)하면, 명령 큐는 그 할당자에 담긴 명령들을 참조한다. 

 

명령 할당자는 ID3D12Device의 다음과 같은 메서드를 이용해서 생성한다.

 

  • type : 이 할당자와 연관시킬 수 있는 명령 목록의 종류.

  • riid : 생성하고자 하는 ID3D12CommandAllocator 인터페이스의 COM ID

  • ppCommandAllocator : 생성된 명령 할당자를 가리키는 포인터. (출력 매개변수)

명령 리스트를 생성하는 메서는 다음과 같다.

 

  • nodeMask : 명령어 리스트를 생성할 대상 어댑터(Node), 하나의 GPU일 때 0, 여러 개의 GPU가 있으면 대상 GPU의 비트를 설정.

  • type : 명령어 리스트의 유형

  • pCommandAllocator : 생성된 명령 리스트에 연관시킬 할당자. 그 명령 할당자의 종류는 명령 리스트의 종류와 일치해야 한다.

  • riid : 생성하고자 하는 명령 리스트에 해당하는 ID3D12CommandList 인터페이스의 COM ID

  • ppCommandList : 생성된 명령 리스트를 가리키는 포인터. (출력 매개변수)

한 할당자를 여러 명령 리스트에 연관 시켜도 되지만, 명령들을 여러 명령 리스트에 동시에 기록할 수는 없다. 다른 말로, 현재 명령들을 추가하는 명령 리스트를 제외한 모든 명령 리스트는 닫혀 있어야 한다. 이렇게 해야 한 명령 리스트의 모든 명령이 할당자 안에 인접해서 저장된다. 명령 리스트를 생성하거나 재설정하면 명령 리스트는 '열린' 상태가 됨을 주의해야 한다. 따라서, 같은 할당자로 두 명령 리스트를 연달아 생성하면 오류가 발생한다.

 

ExecuteCommandLists를 호출한 후 Reset 메서드를 호출하면 새로운 명령들을 기록하는 데 재사용할 수 있게 된다. Reset 메서드의 매개변수는 CreateCommanList의 해당 매개변수들과 의미가 같다.

HRESULT ID3D12CommandList::Reset(
ID3D12CommandAllocator *pAllocator,
ID3D12PipelineState *pInitialState);

이 메서드는 주어진 명령 리스트를 마치 처음 생성했을 때와 같은 상태로 만든다. 이 메서드를 이용하면 명령 리스트를 해제하고 새로이 명령 리스트를 할당하는 번거로움 없이 명령 리스트의 내부 메모리를 재사용할 수 있다. 명령 리스트를 재설정해도 명령 큐에 있는 명령들에는 영향이 미치지 않는다. 명령 큐가 참조하는 명령들은 연관된 명령 할당자의 메모리에 여전히 남아있기 때문이다.

 

C가 p2를 이용해서 기하구조를 그리거나 R이 갱신되는 도중에 기하구조를 그리려 하는 것은 일종의 오류이다. 두 경우 모두, 프로그래머가 의도한 행동은 아니다.

하나의 프레임을 완성하는 데 필요한 렌더링 명령들을 모두 GPU에 제출한 후에는, 명령 할당자의 메모리를 다음 프레임을 위해 재사용해야 할 것이다. 이때 Reset 메서드를 사용한다.

 

이러한 개념은 vector::clear를 호출하는 것과 비슷하다. clear를 호출하면 해당 벡터의 크기가 0이 되지만, 현재 용량(Capacity)는 변하지 않는다. 그러나, 명령 큐가 할당자 안의 자료를 참조하고 있을 수도 있으므로, GPU가 명령 할당자에 담긴 모든 명령을 실행 했음이 확실해지기 전까지는 명령 할당자를 재설정하지 말아햐 한다

반응형
Comments