๐Ÿ™‡โ€โ™€๏ธ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();
}

ํƒœ๊ทธ:

์นดํ…Œ๊ณ ๋ฆฌ:

์—…๋ฐ์ดํŠธ: