- Device 생성
- 4X MSAA 품질 수준 지원 점검 ( 생략 가능 )
- CommandQueue, CommandList 생성
- Fence 생성
- SwapChain 설정
- Descriptor 크기 얻기
- Descriptor Heap 생성, Render Target View 생성, Depth Stencil View 생성
이전 글에서는 Device를 생성하고 장치가 4X MSAA를 지원하는지에 대한 점검을 하였다. 이어서 CommandQueue와 CommandList를 생성하는 작업부터 장치 초기화를 진행해보자.
이전 글에서 말했듯이 DirectX는 CPU에서 명령들을 모아 제출하면 GPU가 순차적으로 명령을 처리하는 식으로 동작하게 된다.
명령들을 담고 있는 CPU의 CommandList와 리스트를 제출 할 GPU의 CommandQueue를 생성하는 작업을 해보자.
ComPtr<ID3D12CommandQueue> mCommandQueue;
// GPU에 명령을 보내기 위해 서술자를 만들어야 함
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
// 서술자를 바탕으로 CommandQueue 생성
ThrowIfFailed(md3dDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&mCommandQueue)));
CommandQueue에 명령들을 보내기 위한 할당자와 리스트를 선언하여 준다.
ComPtr<ID3D12CommandAllocator> mDirectCmdListAlloc;
ComPtr<ID3D12GraphicsCommandList> mCommandList;
한개의 할당자는 한개의 리스트와 매칭되야 하므로, 둘의 타입을 일치해야한다는 것을 유의하며 생성하여주면 된다.
// Allocator 생성
ThrowIfFailed(md3dDevice->CreateCommandAllocator(
D3D2_COMMAND_LIST_TYPE_DIRECT,
IID_PPV_ARGS(&mDirectCmdListAlloc)
));
// List 생성
ThrowIfFailed(md3dDevice->CreateCommandList(
0,
D3D12_COMMAND_LIST_TYPE_DIRECT,
mDirectCmdListAlloc.Get(), // CommandAllocator 매칭
nullptr, // Initial PipelineStateObject
IID_PPV_ARGS(&mCommandList)
));
// 렌더링 시점에서 Open상태로 만들어 줄 것이기에 Close 시켜주기
mCommandList->Close();
CPU와 GPU의 동기화를 위해 Fence 객체를 생성하여준다.
// Fence 객체
ComPtr<ID3D12Fence> mFence;
// 현재 Fence
UINT64 mCurrentFence = 0;
ThrowIfFailed(md3dDevice->CreateFence(
0,
D3D12_FENCE_FLAG_NONR,
IID_PPV_ARGS(&mFence)
));
GPU가 CommandQueued의 명령을 특정 지점까지 처리하는 것을 Flush라고 한다. 이때 CPU는 GPU가 모든 작업을 처리할 때까지 대기하게 된다.
만들어둔 Fence를 이용하여 Flush 함수를 만들고, 후에 렌더링을 진행할 때 CPU와 GPU를 동기화 시켜줄 수 있다.
void FlushCommandQueue() {
// Fence 지점까지 명령을 진행하기 위해 Fence 전진
mCurrentFence++;
// CommandQueue에 명령어를 추가하며, 새 Fence 포인트 설정
ThrowIfFailed(mCommandQueue->Signal(mFence.Get(), mCurrentFence));
// GPU가 지정한 Fence 포인트까지 명령을 처리할 때까지 기다리기
if(mFence->GetCompletedValue() < mCurrentFence) {
HANDLE eventHandle = CreateEventEx(nullptr, false, false, EVENT_ALL_ACCESS);
// GPU가 지정한 Fence 포인트에 도달했을 때 이벤트 발행
ThrowIfFailed(mFence->SetEventOnCompletion(mCurrentFence, eventHandle));
// 이벤트가 발행될 때 (GPU가 지정한 Fence 포인트에 도달했을 떄) 까지 대기하기
WaitForSingleObject(eventHandle, INFINITE);
ColoseHandle(eventHandle);
}
}
SwapChain을 생성하기 위해 SwapChain 서술자를 생성하고자 하는 SwapChain에 맞게 설정하는 작업을 하여야 한다.
// SwapChain 선언
ComPtr<IDXGISwapChain> mSwapChain;
// SwapChain 관련 변수
static const int SwapChainBufferCount = 2; // 사용할 버퍼의 수 (Back, Front)
int mCurBackBuffer = 0; // 현재 백버퍼의 인덱스
ComPtr<ID3D12Resource> mSwapChainBuffers[SwapChainBufferCount] // 사용할 버퍼들
백 버퍼의 위치가 고정이 아니기에 편하게 사용할 수 있도록 현재 백 버퍼를 반환하는 함수를 만들어준다.
ID3D12Resource* GetCurBackBuffer() const {
return mSwapChainBuffer[mCurBackBuffer].Get();
}
mSwapChain.Reset();
// SwapChain 서술사 설정
DXGI_SWAP_CHAIN_DESC scDesc;
// --------------------- BUFFER DESC SETTING ------------------------
// 버퍼의 사이즈 -> 해상도와 매칭되어야 함
scDesc.BufferDesc.Width = mClientWidth;
scDesc.BufferDesc.Height = mClientHeight;
// 1초에 60헤르츠
scDesc.BufferDesc.RefreshRate.Numerator = 60;
scDesc.BufferDesc.RefreshRate.Denominator = 1;
scDesc.BufferDesc.Format = mBackBufferFormat;
sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
// ------------------------------------------------------------------
// --------------------- SAMPLE DESC SETTING ------------------------
sd.SampleDesc.Count = m4xMsaaState ? 4 : 1;
sd.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
// ------------------------------------------------------------------
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // 후면 버퍼를 렌더 타겟용으로 사용
sd.BufferCount = SwapChainBufferCount; // 사용할 버퍼 갯수
sd.OutputWindow = mhMainWnd; // 윈도우 핸들
sd.Windowed = true; // 창모드 활성화 여부
sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; // Present 후 백 버퍼 DISCARD
sd.Flags = DXGI_SWAP_CHAIN_FLAG_SWITCH; // 디스플레이 모드 변경 가능
// SwapChain 생성
ThrowIfFailed(mdxgiFactory->CreateSwapChain(
mCommandQueue.Get(),
&scDesc,
&mSwapChain
));
// 버퍼 배열 채우기
for(int i = 0; i < SwapChainBufferCount; ++i) {
ThrowIfFailed(mSwapChain->GetBuffer(i, IID_PPV_ARGS(&mSwapChainBuffers[i]));
}