본문 바로가기

DirectX

[DirectX 12] 명령 대기열

명령 대기열(Command Queue)
CPU와 GPU간의 상호 작용을 하며 최적의 성능을 뽑아내기 위해서는 최대한 둘 다 바쁘게 돌아가야 한다.
즉 2가지 조건을 만족 해야 한다.
1. 병렬로 작동한다.
2. 동기화를 최소화 한다.
동기화를 최소화하는 이유는 간단하게 말해서 동기화가 일어나서 버스에서 인터럽트가 일어나서 하나의 장치가 활동을 하는 동안 다른 장치들이 놀고 있어야 한다면 이는 굉장히 성능 낭비라는 것이다.

그래서 DirectX 12에서는 GPU에 명령 대기열을 두고 CPU에서는 명령 목록(Command List)를 두어 Direct3D API를 이용해서 이 명령 목록에 그리기 명령들을 넣게 된다. 그리고 이를 명령 대기열에 제출한다. 이는 GPU가 명령 대기열에 있는 실행할 준비가 되었다면 그 시기부터 이 명령들을 처기하기 시작한다. 이를 통해서 동기화를 최소화할 수 있는 것이다.

만약 이 큐가 비면 GPU가 놀 것이고, 반대로 다 차버리면 CPU가 놀게 될 것이다. 이 상황 전부 다 성능에 좋은 상황이 아니기 때문에 둘 다 쉬지 않고 돌아가게 만들어야 한다.

이제 이 명령 대기열과 관련된 객체들이 다음과 같이 존재한다.
1. Command Queue : ID3D12CommandQueue로 대표되는 인터페이스를 가지고 있다. 이 인터페이스를 통해서 명령 대기열을 생성하고 설정한다.
2. Command List : 명령 목록이다. CPU에서는 이 객체를 이용해서 그리기 명령들을 넣게 된다. 
3. Command Allocator : 커맨드 할당자라 부른다.  이 할당자는 커맨드 리스트의 메모리를 할당하기 위해 존재한다. 또 명령 목록에 추가된 명령들은 이 할당자의 메모리에 저장된다.

 // 커맨드 큐에 대한 서술자를 채우고 생성한다.
    D3D12_COMMAND_QUEUE_DESC queueDesc = {};
    queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
    queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
    ThrowIfFailed(m_d3dDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&m_CommandQueue)));

    // 커맨드 할당자를 생성한다.
    // 이 할당자는 커맨드 리스트의 메모리를 할당하기 위해 존재한다.
    // 명령 목록에 추가된 명령들은 이 할당자의 메모리에 저장된다.
    ThrowIfFailed(m_d3dDevice->CreateCommandAllocator(
        D3D12_COMMAND_LIST_TYPE_DIRECT, // GPU가 직접 실행하는 명령 목록
        IID_PPV_ARGS(m_DirectCmdListAlloc.GetAddressOf())));    // RIID와 void** 타입의 커맨드 할당자 COM 객체를 넣어준다.

    // 커맨드 큐에 넣을 커맨드 리스트를 생성한다.
    ThrowIfFailed(m_d3dDevice->CreateCommandList(
        0,
        D3D12_COMMAND_LIST_TYPE_DIRECT,
        m_DirectCmdListAlloc.Get(),     // 관련된 커맨드 할당자를 넣는다.
        nullptr,                        // PipelineStateObject를 초기화한다.
        IID_PPV_ARGS(m_CommandList.GetAddressOf())));

    // 닫힌 상태로 시작을 한다.
    // 처음 커맨드 리스트를 참조할 때 이를 리셋할 것이다.
    // 그리고 커맨드 리스트는 리셋하기 전에는 닫혀있어야 한다.
    m_CommandList->Close();



한 할당자를 여러 명령 목록에 묶어도 되지만, 하나의 명령을 여러 명령 목록에 동시에 기록할 수는 없다.
이 말은 결국 현재 명령들을 추가하는 명령 목록을 제외한 다른 명령 목록은 전부 닫혀있어야 한다는 것이다.
명령 목록을 생성하거나 재설정하면 이는 열려있는 것으로 간주되어 이에 대한 오류를 발생시킬 수 있다.

만약 명령 목록의 메모리를 재사용 하고 싶다면, ID3D12CommandQueue::ExecuteCommandList(C)를 호출한 뒤 ID3D12CommandQueue::Reset 메서드를 호출해서 C의 내부 메모리들을 새로운 명령들을 기록하는데 재사용할 수 있다. 이 메서드를 통해서 명령 목록을 처음 생성했을 때와 같이 만들 수 있다. 다만 이렇게 재설정을 하더라도, 명령 대기열에 있는 명령들에는 영향을 미치지 않는다는 것은 기억을 해야한다.

'DirectX' 카테고리의 다른 글

[DirectX 12] 렌더링 파이프라인  (0) 2022.02.15
[DirectX 12] Direct3D 초기화  (0) 2022.01.10
[DirectX 12] DirectX 기초 지식  (0) 2022.01.04