드디어 장치 초기화를 끝내고~~ 이제 알록달록... 은 무리라도 단색 화면을 출력할 수 있는 바로 직전 단계까지 왔다! 사실 오늘 포스팅의 궁극적인 목적은 단색 화면 렌더링이 아닌, 렌더링 세팅이다!
매 프레임마다 순서대로 호출되는 DrawBegin, Draw, DrawEnd 함수가 있다. 오늘은 Draw Begin과 Draw End에서 Command List의 명령들을 Queue로 보내고 초기화하며 렌더링할 것이다.
렌더링 전 (Draw Begin)
mDirectCmdListAlloc->Reset();
mCommandList->Reset(mDirectCmdListAlloc.Get(), nullptr);
먼저, 할당자를 리셋해주는데 GPU가 할당된 명령을 모두 끝낸 경우에만 리셋할 수 있다. Command List도 리셋 함수를 호출해 Open 상태로 만들어준다.
// Indicate a state transition on the resource usage.
mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(),
D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));
본래 리소스를 통해 어떤 명령을 실행하기 위해서는, 리소스가 특정 상태에 있어야 한다. Back Buffer를 렌더링하기 위해서는 렌더 타겟 상태 (D3D12_RESOURCE_STATE_RENDER_TARGET)가 되어야 한다!
이제 Back Buffer의 상태가 전이했음을 Resource Barrier를 통해서 GPU에게 알렸다.
// Set the viewport and scissor rect. This needs to be reset whenever the command list is reset.
mCommandList->RSSetViewports(1, &mScreenViewport);
mCommandList->RSSetScissorRects(1, &mScissorRect);
저번 포스팅에서 Viewport와 ScissorRects는 CmdList가 리셋될 때마다 세팅해주어야한다고 말했으므로, Draw 전에 항상 세팅해주었다.
// Clear the back buffer and depth buffer.
mCommandList->ClearRenderTargetView(CurrentBackBufferView(), Colors::LightPink, 0, nullptr);
mCommandList->ClearDepthStencilView(DepthStencilView(), D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr);
후면 버퍼와 깊이 버퍼를 깔끔하게 싹 초기화해주었다. 후면 버퍼는 연한 분홍 색으로 칠해주었다. Depth Stencil은 Depth와 Stencil을 각각 1과 0으로 초기화해주었다.
렌더링 후 (Draw Begin)
// Indicate a state transition on the resource usage.
mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(),
D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));
렌더링 전에 전이한 후면 버퍼의 상태를 다시 원래대로 디폴트 값으로 돌려주었다. 이제 전면과 후면 버퍼의 역할을 바꿔주어야 한다.
// Done recording commands.
mCommandList->Close();
가장 중요한 Close! CmdList를 닫지 않으면, Execute도 먹히지 않는 법이기 때문에, 커맨드 리스트를 닫아주었다.
ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };
mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);
그리고 Command List에 있던 내용들을 CommandQueue에 올려주었다.
// swap the back and front buffers
mSwapChain->Present(0, 0);
mCurBackBuffer = (mCurBackBuffer + 1) % SwapChainBufferCount;
전면, 후면 버퍼를 스왑해주는 것도 잊으면 안 된다! Present를 호출해 둘을 플리핑했다. mCurBackBuffer에 바뀐 후면 버퍼의 인덱스를 계속 계속 돌아가도록 설정해주었다.
Flush
CPU와 GPU를 동기화하기 위해 장치 초기화 때 만들어주었던 Fence를 사용할 때가 되었다.
GPU가 Command Queue의 명령 중 특정 지점까지의 모든 명령을 다 처리하는 것을 Flush라고 하고, 이를 CPU가 기다리는 방식이다.
void D3DApp::FlushCommandQueue()
{
// 현재 지점을 갱신
mCurrentFence++;
// 새 펜스 갱신 명령 추가
mCommandQueue->Signal(mFence.Get(), mCurrentFence);
// GPU가 이 펜스 지점까지의 명령들을 완료할 때까지 기다린다
if (mFence->GetCompletedValue() < mCurrentFence)
{
HANDLE eventHandle = CreateEventEx(nullptr, false, false, EVENT_ALL_ACCESS);
// 현재 펜스에 도달했을 때 실행되는 이벤트 설정
mFence->SetEventOnCompletion(mCurrentFence, eventHandle);
// 펜스에 도달할 때까지 기다리기
WaitForSingleObject(eventHandle, INFINITE);
CloseHandle(eventHandle);
}
}
이러면 CPU가 GPU를 기다리게 되며 동기화할 수 있다.
실행한면, 아까 설정한 연한 분홍색의 화면을 얻을 수 있다!
색상이 많아 원하는 색상으로 채울 수도 있었다.
오늘 렌더링 준비와 초기화 등등을 배울 수 있었다. 앞으로 갈 길이 훨씬 많다. (진짜로) 파이팅 해야지! 다음에는 렌더링 파이프라인 상태를 설정해볼 것이다!
참고
'Direct X' 카테고리의 다른 글
[DX12] 로컬 > 월드 > 뷰 > 투영 이론 (1) | 2023.02.27 |
---|---|
[DX12] 렌더링 파이프라인에 대해 알아보자! (3) | 2023.02.27 |
[DX12] 장치를 초기화 해보자! (3) (RTV, DSV, Viewport) (0) | 2023.02.26 |
[DX12] 장치를 초기화 해보자! (2) (Command Object, Swap Chain) (0) | 2023.02.13 |
[DX12] 장치를 초기화 해보자! (1) (Device, Fence, Desc Size, MSAA) (0) | 2023.02.13 |