CommandQueue๋?
๐โโ๏ธCommandQueue
DirectX12์ด์ ์๋ ์ผ๊ฐ์ ์ฃผ๋ฉด ๋ฐ๋ก๋ฐ๋ก ์ผ์ ํ๋๋ฐ DirectX12๋ก ๋์ด์ค๋ฉด์ ์ผ๊ฐ ๋ชฉ๋ก์ ๋๊ธฐ๊ณ ๊ทธ๊ฒ์ผ๋ก ์ผ์ ์ํํด์ผํ๋ค.
๐ชCommandQueue.h ๋ถ์
CommandQueue.h ์ ์ฒด์ฝ๋
#pragma once
// ์ ๋ฐฉ์ ์ธ!
class SwapChain;
class DescriptorHeap;
class CommandQueue
{
public:
~CommandQueue();
void Init(ComPtr<ID3D12Device> device, shared_ptr<SwapChain> swapChain, shared_ptr<DescriptorHeap> descHeap);
void WaitSync();
void RenderBegin(const D3D12_VIEWPORT* vp, const D3D12_RECT* rect);
void RenderEnd();
ComPtr<ID3D12CommandQueue> GetCmdQueue() { return _cmdQueue; }
private:
// CommandQueue : DX12์ ๋ฑ์ฅ
// ์ธ์ฃผ๋ฅผ ์์ฒญํ ๋, ํ๋์ฉ ์์ฒญํ๋ฉด ๋นํจ์จ์
// [์ธ์ฃผ ๋ชฉ๋ก]์ ์ผ๊ฐ์ ์ฐจ๊ณก์ฐจ๊ณก ๊ธฐ๋กํ๋ค๊ฐ ํ ๋ฐฉ์ ์์ฒญํ๋ ๊ฒ
ComPtr<ID3D12CommandQueue> _cmdQueue;
ComPtr<ID3D12CommandAllocator> _cmdAlloc;
ComPtr<ID3D12GraphicsCommandList> _cmdList;
// Fence : ์ธํ๋ฆฌ(?)
// CPU / GPU ๋๊ธฐํ๋ฅผ ์ํ ๊ฐ๋จํ ๋๊ตฌ
ComPtr<ID3D12Fence> _fence;
uint32 _fenceValue = 0;
HANDLE _fenceEvent = INVALID_HANDLE_VALUE;
shared_ptr<SwapChain> _swapChain;
shared_ptr<DescriptorHeap> _descHeap;
};
์ปค๋งจ๋ ํจํด๊ณผ ๋น์ทํ๋ค.
์ผ๊ฐ์ด ์๊ธฐ๋ฉด ๋น์ฅ ์ฒ๋ฆฌํ์ง ์๊ณ , ๋์ค์ ์ฒ๋ฆฌํ ์ ์๊ฒ Queue์ ์ผ๊ฐ์ ๋ฃ๊ณ ๋์ค์ ๋๊ตฐ๊ฐ๊ฐ ์ผ๊ฐ์ ์ฒ๋ฆฌํ๋ ๊ฐ๋
.
ComPtr<ID3D12CommandQueue>
์ ์ผ๊ฐ์ ๋ฃ์ด์ฃผ๊ณ , ComPtr<ID3D12CommandAllocator>
๋ ํ ๋น์์ธ๋ฐ ๋ฉ๋ชจ๋ฆฌ ๊ณต๊ฐ์ ๊ด๋ฆฌํ๋ ๊ฒ์ด๋ค.
ComPtr<ID3D12GraphicsCommandList>
๋ ์ผ๊ฐ ๋ชฉ๋ก์ ๊ด๋ฆฌํ๋ค. ์์ฃผ ์ฌ์ฉ๋ ๊ฒ ์ค์ ํ๋์ด๋ค.
Fence๋ ๋ง์ด๋ํ๋ค๊ณ ํ๋ค.
CPU์ GPU๋ฅผ ๋๊ธฐํ ์์ผ์ฃผ๋ ๊ฐ์ฅ ๊ฐ๋จํ ๋๊ตฌ ์ค์ ํ๋์ด๋ค.
๊ตฌํ๋ถ์์ ์ฝ๋๋ฅผ ๋ณด๋ฉด์ ์์ธํ ๋ถ์ํด๋ณด์.
## ๐ชCommandQueue.cpp ๋ถ์
CommandQueue.cpp ์ ์ฒด์ฝ๋
#include "pch.h"
#include "CommandQueue.h"
#include "SwapChain.h"
#include "DescriptorHeap.h"
CommandQueue::~CommandQueue()
{
::CloseHandle(_fenceEvent);
}
void CommandQueue::Init(ComPtr<ID3D12Device> device, shared_ptr<SwapChain> swapChain, shared_ptr<DescriptorHeap> descHeap)
{
_swapChain = swapChain;
_descHeap = descHeap;
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&_cmdQueue));
// - D3D12_COMMAND_LIST_TYPE_DIRECT : GPU๊ฐ ์ง์ ์คํํ๋ ๋ช
๋ น ๋ชฉ๋ก
device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&_cmdAlloc));
// GPU๊ฐ ํ๋์ธ ์์คํ
์์๋ 0์ผ๋ก
// DIRECT or BUNDLE
// Allocator
// ์ด๊ธฐ ์ํ (๊ทธ๋ฆฌ๊ณ ๋ช
๋ น์ nullptr๋ก ์ง์ )
device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, _cmdAlloc.Get(), nullptr, IID_PPV_ARGS(&_cmdList));
// CommandList๋ Close / Open ์ํ๊ฐ ์๋๋ฐ
// Open ์ํ์์ Command๋ฅผ ๋ฃ๋ค๊ฐ Closeํ ๋ค์ ์ ์ถํ๋ ๊ฐ๋
_cmdList->Close();
// CreateFence
// - CPU์ GPU์ ๋๊ธฐํ ์๋จ์ผ๋ก ์ฐ์ธ๋ค
device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&_fence));
_fenceEvent = ::CreateEvent(nullptr, FALSE, FALSE, nullptr);
}
void CommandQueue::WaitSync()
{
// Advance the fence value to mark commands up to this fence point.
_fenceValue++;
// Add an instruction to the command queue to set a new fence point.
// Because we are on the GPU timeline, the new fence point won't be set until the GPU finishes
// processing all the commands prior to this Signal().
_cmdQueue->Signal(_fence.Get(), _fenceValue);
// Wait until the GPU has completed commands up to this fence point.
if (_fence->GetCompletedValue() < _fenceValue)
{
// Fire event when GPU hits current fence.
_fence->SetEventOnCompletion(_fenceValue, _fenceEvent);
// Wait until the GPU hits current fence event is fired.
::WaitForSingleObject(_fenceEvent, INFINITE);
}
// ํจ์จ์ ์ธ ๋ฐฉ์์ ์๋
// CPU๊ฐ GPU์ ์ผ์ด ๋๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ๊ณ ์์
}
void CommandQueue::RenderBegin(const D3D12_VIEWPORT* vp, const D3D12_RECT* rect)
{
// vector.clear()์ฒ๋ผ capacity๋ ๋จ์์๋ ๊ฐ๋
. ๋ฉ๋ชจ๋ฆฌ๊ฐ ์ค์ด๋ค์ง ์๋๋ค.
_cmdAlloc->Reset();
_cmdList->Reset(_cmdAlloc.Get(), nullptr);
D3D12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(
_swapChain->GetCurrentBackBufferResource().Get(),
D3D12_RESOURCE_STATE_PRESENT, // ํ๋ฉด ์ถ๋ ฅ
D3D12_RESOURCE_STATE_RENDER_TARGET); // ์ธ์ฃผ ๊ฒฐ๊ณผ๋ฌผ
_cmdList->ResourceBarrier(1, &barrier);
// Set the viewport and scissor rect, This needs to be reset whenever the command list is reset.
_cmdList->RSSetViewports(1, vp);
_cmdList->RSSetScissorRects(1, rect);
// Specify the buffers we are going to render to.
D3D12_CPU_DESCRIPTOR_HANDLE backBufferView = _descHeap->GetBackBufferView();
_cmdList->ClearRenderTargetView(backBufferView, Colors::LightSteelBlue, 0, nullptr);
_cmdList->OMSetRenderTargets(1, &backBufferView, FALSE, nullptr);
}
void CommandQueue::RenderEnd()
{
D3D12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(
_swapChain->GetCurrentBackBufferResource().Get(),
D3D12_RESOURCE_STATE_RENDER_TARGET, // ์ธ์ฃผ ๊ฒฐ๊ณผ๋ฌผ
D3D12_RESOURCE_STATE_PRESENT); // ํ๋ฉด ์ถ๋ ฅ
_cmdList->ResourceBarrier(1, &barrier);
_cmdList->Close();
// ์ปค๋งจ๋ ๋ฆฌ์คํธ ์ํ
ID3D12CommandList* cmdListArr[] = { _cmdList.Get() };
_cmdQueue->ExecuteCommandLists(_countof(cmdListArr), cmdListArr);
_swapChain->Present();
// Wait until frame commands are complete. This waiting is inefficient and is done for simplicity.
// Later we will show how to organize our rendering code so we do not have to wait per frame.
WaitSync();
_swapChain->SwapIndex();
}
ํจ์๋ณ๋ก ๊ตฌ๋ถํด์ ๋ถ์ํด๋ณด์.
Init
void CommandQueue::Init(ComPtr<ID3D12Device> device, shared_ptr<SwapChain> swapChain, shared_ptr<DescriptorHeap> descHeap)
{
_swapChain = swapChain;
_descHeap = descHeap;
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&_cmdQueue));
// - D3D12_COMMAND_LIST_TYPE_DIRECT : GPU๊ฐ ์ง์ ์คํํ๋ ๋ช
๋ น ๋ชฉ๋ก
device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&_cmdAlloc));
// GPU๊ฐ ํ๋์ธ ์์คํ
์์๋ 0์ผ๋ก
// DIRECT or BUNDLE
// Allocator
// ์ด๊ธฐ ์ํ (๊ทธ๋ฆฌ๊ณ ๋ช
๋ น์ nullptr๋ก ์ง์ )
device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, _cmdAlloc.Get(), nullptr, IID_PPV_ARGS(&_cmdList));
// CommandList๋ Close / Open ์ํ๊ฐ ์๋๋ฐ
// Open ์ํ์์ Command๋ฅผ ๋ฃ๋ค๊ฐ Closeํ ๋ค์ ์ ์ถํ๋ ๊ฐ๋
_cmdList->Close();
// CreateFence
// - CPU์ GPU์ ๋๊ธฐํ ์๋จ์ผ๋ก ์ฐ์ธ๋ค
device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&_fence));
_fenceEvent = ::CreateEvent(nullptr, FALSE, FALSE, nullptr);
}
device๊ฐ engine์์์ ํต์ฌ์ด๋ผ๊ณ ํ๋๋ฐ CommandQueue์ ๋ถํ๋ค ์ญ์ device๋ฅผ ํตํด์ ๋ง๋ค์ด์ฃผ๊ณ ์๋ค.
D3D12_COMMAND_QUEUE_DESC
๋ผ๋ CommandQueue์ ์ค๋ช
์๋ฅผ ์์ฑํ๊ณ
device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&_cmdQueue));
๋ฅผ ํตํด์ ์ค์ ๋ก CommandQueue๋ฅผ ์ด๊ธฐํํ๋ค.
์ด๊ธฐํ๋ Device๋์ ๋ง์ฐฌ๊ฐ์ง๋ก IID_PPV_ARGS
๋ฅผ ์ฌ์ฉํ๊ณ ์๋ค.
์ธ๋ถ์ ์ผ๋ก ํ๋ ๊ธฐ๋ฅ๋ค์ ๊ตณ์ด ์์๋ณด์ง ์์๋ ๋๋ค๊ณ ํ๋ค.
CommandList ์์ฑํ๋ ๊ณณ์ ๋ณด๋ฉด,
device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, _cmdAlloc.Get(), nullptr, IID_PPV_ARGS(&_cmdList));
์ธ๋ฐ ์์์ ์์ฑํ _cmdAlloc
์ ์ฌ์ฉํ์ฌ ๋ฉ๋ชจ๋ฆฌ ๊ณต๊ฐ์ ํ๋ณดํ๊ณ ์๋ค.
stl::vector์ ๋น์ทํ ๊ฐ๋
์ด๋ผ๊ณ ํ๋ค.
๋น์ ๋ฅผ ํ์๋ฉด vector.size๊ฐ _cmdList์ ๋น์ทํ๊ณ vector.capacity๊ฐ _cmdAlloc๊ณผ ๋น์ทํ ๊ฐ๋
์ด๋ค.
๊ทธ๋ฆฌ๊ณ ์ผ๊ฐ ๋ชฉ๋ก์์๋ Close๋ฅผ ํด์ ์ผ๊ฐ ๋ชฉ๋ก์ ์ ์ถํ ์ค๋น๋ฅผ ์๋ฃํ๋ค.
device๋ฅผ ํตํด Fence์ญ์ ์์ฑํ๋๋ฐ ์์ฑ๋ง ํ๊ณ ์๋ค.
::CreateEvent
๋ DirectX์์๋ง ๊ตญํ๋๊ฒ ์๋๋ผ ๋ฉํฐ์ฐ๋ ๋ ๋ฑ ๋ค์ํ๊ฒ ์ฌ์ฉํ๋ค.
์ด๋ฒคํธ๋ ์ ํธ๋ฑ์ผ๋ก ๋น์ ๋ฅผ ํ๋๋ฐ ๋นจ๊ฐ๋ถ์ผ ๋๋ ์๋ฌด๊ฒ๋ ์ํ๋ค๊ฐ ํ๋๋ถ์ผ ๋๋ ์ด๋์ํ๋ค.
์ด๊ฒ๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก ํน์ ์กฐ๊ฑด์ด ์ฌ ๋๊น์ง ๊ธฐ๋ค๋ ธ๋ค๊ฐ ์กฐ๊ฑด์ ๋ง์กฑํ๋ฉด ์ผ์ ์ํํ๋ค.
๊ทธ๋ฆฌ๊ณ ์๋ฉธ์์์ ์ด๋ฒคํธ๋ฅผ ๊บผ์ฃผ๋ ์ผ๋ ํ๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
WaitSync
void CommandQueue::WaitSync()
{
// Advance the fence value to mark commands up to this fence point.
_fenceValue++;
// Add an instruction to the command queue to set a new fence point.
// Because we are on the GPU timeline, the new fence point won't be set until the GPU finishes
// processing all the commands prior to this Signal().
_cmdQueue->Signal(_fence.Get(), _fenceValue);
// Wait until the GPU has completed commands up to this fence point.
if (_fence->GetCompletedValue() < _fenceValue)
{
// Fire event when GPU hits current fence.
_fence->SetEventOnCompletion(_fenceValue, _fenceEvent);
// Wait until the GPU hits current fence event is fired.
::WaitForSingleObject(_fenceEvent, INFINITE);
}
// ํจ์จ์ ์ธ ๋ฐฉ์์ ์๋
// CPU๊ฐ GPU์ ์ผ์ด ๋๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ๊ณ ์์
}
์ด๋ค ๊ฐ์ ์ฃผ๊ณ ๊ทธ ์ผ์ด ๋๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ๋ ๋ฐฉ์์ด๋ค.
ํจ์จ์ ์ธ ๋ฐฉ์์ด ์๋๋ค!
CPU๊ฐ GPU์ ์ผ์ด ๋๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ๋ ๋ฐฉ์์ด๊ธฐ ๋๋ฌธ์ด๋ค.
๊ฒ์์ ์ค์๊ฐ์ผ๋ก ๊ณ์ ์ฐ์ฐ์ ํด์ผํ๋๋ฐ ์ง๊ธ์ ๊ฐ๋จํ๊ฒ ๊ณต๋ถํ๊ธฐ ์ํด์ ์ด๋ ๊ฒ ์งฐ๋ค.
RenderBegin
void CommandQueue::RenderBegin(const D3D12_VIEWPORT* vp, const D3D12_RECT* rect)
{
// vector.clear()์ฒ๋ผ capacity๋ ๋จ์์๋ ๊ฐ๋
. ๋ฉ๋ชจ๋ฆฌ๊ฐ ์ค์ด๋ค์ง ์๋๋ค.
_cmdAlloc->Reset();
_cmdList->Reset(_cmdAlloc.Get(), nullptr);
D3D12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(
_swapChain->GetCurrentBackBufferResource().Get(),
D3D12_RESOURCE_STATE_PRESENT, // ํ๋ฉด ์ถ๋ ฅ
D3D12_RESOURCE_STATE_RENDER_TARGET); // ์ธ์ฃผ ๊ฒฐ๊ณผ๋ฌผ
_cmdList->ResourceBarrier(1, &barrier);
// Set the viewport and scissor rect, This needs to be reset whenever the command list is reset.
_cmdList->RSSetViewports(1, vp);
_cmdList->RSSetScissorRects(1, rect);
// Specify the buffers we are going to render to.
D3D12_CPU_DESCRIPTOR_HANDLE backBufferView = _descHeap->GetBackBufferView();
_cmdList->ClearRenderTargetView(backBufferView, Colors::LightSteelBlue, 0, nullptr);
_cmdList->OMSetRenderTargets(1, &backBufferView, FALSE, nullptr);
}
RenderEnd
void CommandQueue::RenderEnd()
{
D3D12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(
_swapChain->GetCurrentBackBufferResource().Get(),
D3D12_RESOURCE_STATE_RENDER_TARGET, // ์ธ์ฃผ ๊ฒฐ๊ณผ๋ฌผ
D3D12_RESOURCE_STATE_PRESENT); // ํ๋ฉด ์ถ๋ ฅ
_cmdList->ResourceBarrier(1, &barrier);
_cmdList->Close();
// ์ปค๋งจ๋ ๋ฆฌ์คํธ ์ํ
ID3D12CommandList* cmdListArr[] = { _cmdList.Get() };
_cmdQueue->ExecuteCommandLists(_countof(cmdListArr), cmdListArr);
_swapChain->Present();
// Wait until frame commands are complete. This waiting is inefficient and is done for simplicity.
// Later we will show how to organize our rendering code so we do not have to wait per frame.
WaitSync();
_swapChain->SwapIndex();
}