공부중

[DirectX12]Hello Wolrd Sample - D3D12HelloTriangle - 6 본문

Programing/DirectX

[DirectX12]Hello Wolrd Sample - D3D12HelloTriangle - 6

곤란 2018. 5. 29. 21:12
반응형

 

// Main message handler for the sample.
LRESULT CALLBACK Win32Application::WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	DXSample* pSample = reinterpret_cast<DXSample*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));

	switch (message)
	{
	case WM_CREATE:
		{
			// Save the DXSample* passed in to CreateWindow.
			LPCREATESTRUCT pCreateStruct = reinterpret_cast<LPCREATESTRUCT>(lParam);
			SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pCreateStruct->lpCreateParams));
		}
		return 0;

	case WM_KEYDOWN:
		if (pSample)
		{
			pSample->OnKeyDown(static_cast<UINT8>(wParam));
		}
		return 0;

	case WM_KEYUP:
		if (pSample)
		{
			pSample->OnKeyUp(static_cast<UINT8>(wParam));
		}
		return 0;

	case WM_PAINT:
		if (pSample)
		{
			pSample->OnUpdate();
			pSample->OnRender();
		}
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}

	// Handle any messages the switch statement didn't.
	return DefWindowProc(hWnd, message, wParam, lParam);
}

 

저번 프로젝트와 똑같이 WindowProc안의 WM_PAINT 메시지일때 OnUpdate와 OnRender가 돌고 있다.

먼저 OnUpdate를 보자.

 

// Update frame-based values.
void D3D12HelloTriangle::OnUpdate()
{
}

 

특별한것 없이 삼각형만 그리기 때문에 OnUpdate에서는 할일이 없다.

이제 OnRender를 보자.

 

 

// Render the scene.
void D3D12HelloTriangle::OnRender()
{
	// Record all the commands we need to render the scene into the command list.
	PopulateCommandList();

	// Execute the command list.
	ID3D12CommandList* ppCommandLists[] = { m_commandList.Get() };
	m_commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);

	// Present the frame.
	ThrowIfFailed(m_swapChain->Present(1, 0));

	WaitForPreviousFrame();
}

 

여기까지만 보면 OnRender는 이전 프로젝트와 다른코드가 하나도 없다.

먼저 PopulateCommandList를 보자.

 

 

void D3D12HelloTriangle::PopulateCommandList()
{
	// Command list allocators can only be reset when the associated 
	// command lists have finished execution on the GPU; apps should use 
	// fences to determine GPU execution progress.
	ThrowIfFailed(m_commandAllocator->Reset());

	// However, when ExecuteCommandList() is called on a particular command 
	// list, that command list can then be reset at any time and must be before 
	// re-recording.
	ThrowIfFailed(m_commandList->Reset(m_commandAllocator.Get(), m_pipelineState.Get()));

	// Set necessary state.
	m_commandList->SetGraphicsRootSignature(m_rootSignature.Get());
	m_commandList->RSSetViewports(1, &m_viewport);
	m_commandList->RSSetScissorRects(1, &m_scissorRect);

	// Indicate that the back buffer will be used as a render target.
	m_commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_renderTargets[m_frameIndex].Get(), 
										D3D12_RESOURCE_STATE_PRESENT, 
										D3D12_RESOURCE_STATE_RENDER_TARGET));

	CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_rtvHeap->GetCPUDescriptorHandleForHeapStart(), m_frameIndex, m_rtvDescriptorSize);
	m_commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr);

	// Record commands.
	const float clearColor[] = { 0.0f, 0.2f, 0.4f, 1.0f };
	m_commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
	m_commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
	m_commandList->IASetVertexBuffers(0, 1, &m_vertexBufferView);
	m_commandList->DrawInstanced(3, 1, 0, 0);

	// Indicate that the back buffer will now be used to present.
	m_commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_renderTargets[m_frameIndex].Get(), 
										D3D12_RESOURCE_STATE_RENDER_TARGET, 
										D3D12_RESOURCE_STATE_PRESENT));

	ThrowIfFailed(m_commandList->Close());
}

몇줄 추가 된거는 없지만 차근차근 살펴보자.

 

 

	// Command list allocators can only be reset when the associated 
	// command lists have finished execution on the GPU; apps should use 
	// fences to determine GPU execution progress.
	ThrowIfFailed(m_commandAllocator->Reset());

먼저 m_commandAllocator->Reset() 을 해주고 있는데 (ID3D12CommandAllocator::Reset) 

주석에 나온대로 메모리를 재사용하기 위해서 호출하는 메서드이다. 

(흔히 std::vector::clear와 비슷하다고 생각하면 된다. - 내부 내용은 비워버리나 capacity(할당공간)은 그대로 둠.)

 

 

	// However, when ExecuteCommandList() is called on a particular command 
	// list, that command list can then be reset at any time and must be before 
	// re-recording.
	ThrowIfFailed(m_commandList->Reset(m_commandAllocator.Get(), m_pipelineState.Get()));

m_commandList->Reset(ID3D12GraphicsCommandList::Reset)은 새 명령리스트가 방금 생성 된 것처럼 명령리스트를 초기 상태로 재설정한다.

 

	// Set necessary state.
	m_commandList->SetGraphicsRootSignature(m_rootSignature.Get());
	m_commandList->RSSetViewports(1, &m_viewport);
	m_commandList->RSSetScissorRects(1, &m_scissorRect);

 

주석은 필요한 상태를 설정한다고 되어 있다.

먼저 SetGraphicsRootSignature를 이용해서  멤버변수에 있는 root signatue(루트 서명) m_rootSignature를 넘겨주고 있다.

 

RSSetViewports를 이용해서 파이프라인의 래스터라이저 단계에 뷰포트의 배열을 바인딩 한다.

첫번째 인자는 바인딩할 뷰포트의 수 이다.

범위는 0~16까지 이다. (0부터 D3D12_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE(16으로 define되어있다.) 까지)

두번째 인자는 디바이스에 바인드 하는 D3D12_VIEWPORT구조체의 배열이 들어간다.

 

RSSetScissorRects라고 있는데 뷰포트는 많이 들어봤는데 ScissorRect(가위 직사각형)은 뭔가 해서 찾아봤더니

ScissorRect는 특정 픽셀들을 선별(culling)하는 용도로 쓰인다고 한다.

BackBuffer(후면 버퍼)를 기준으로 가위 직사각형을 정의, 설정하면 

렌더링시 가위 직사각형 바깥의 픽셀들은 BackBuffer에 레스터화 되지 않는다고 한다.

commandList를 Reset하면 ScissorRect도 재설정 해야 한다.

 

메소드의 인자는 RSSetViewports와 똑같다고 보면 된다.

 

	// Indicate that the back buffer will be used as a render target.
	m_commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_renderTargets[m_frameIndex].Get(), 
										D3D12_RESOURCE_STATE_PRESENT, 
										D3D12_RESOURCE_STATE_RENDER_TARGET));

	CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_rtvHeap->GetCPUDescriptorHandleForHeapStart(), m_frameIndex, m_rtvDescriptorSize);
	m_commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr);

 

저번에 설명하였듯이 리소스를 무분별하게 사용하게 되는 부분을 막기위해서 리소스에 상태(state)를 부여하게 되는데 임의의 상태 전이를 Direct3D에게 알려줌으로써 자원 위험 상황을 막아주고 이 상태전이를 알려주기 위해서 전이자원장벽(transition resourece barrier)를 이용하게 된다.

역시나 저번 코드와 똑같이 ResourceBarrier에서 BackBuffer가 렌더링 대상으로 사용될 것임을 나타내주고 있다.

 

CD3DX12_CPU_DESCRIPTOR_HANDLE형의 rtvHandle을 만들어 주고. 바로 아래에

m_commandList->OMSetRenderTargets를 이용해서 렌더링에 사용할 렌더 대상과 깊이 스텐실 버퍼를 파이프라인에 묶는다.

(OMSetRenderTargets 메서드가 렌더링에 사용할 렌더 대상과 깊이 스텐실 버퍼를 파이프라인에 묶는 역활을 한다.)

 

첫번째 인자로는 파이프라인에 묶을 렌더 대상들을 서술하는 RTV들의 갯수.

두번째 인자는 파이프라인에 묶을 렌더 대상들을 서술하는 RTV들의 배열을 가리키는 포인터.

세번째 인자는 배열의 모든 RTV가 서술자 힙 안에서 연속적으로 저장되어 있다면 true 아니면 false

네번째 인자는 파이프라인에 묶을 깊이 스텐실 버퍼를 서술하는 DSV를 가리키는 포인터.

 

여기서는 DSV(Depth Stencil View)가 없으므로 nullptr을 넘겨준것 같다.

 

	// Record commands.
	const float clearColor[] = { 0.0f, 0.2f, 0.4f, 1.0f };
	m_commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
	m_commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
	m_commandList->IASetVertexBuffers(0, 1, &m_vertexBufferView);
	m_commandList->DrawInstanced(3, 1, 0, 0);

 

배경색(?)을 지정해주는 컬러값 float 배열을 만들어 주었고.

먼저 ClearRenderTargetView를 통해서 위에서 지정해준 컬러값으로 RTV를 칠해주었다.

 

다음 라인을 들어가기전에 정점(Vertex)들은 Vertex Buffer(정점 버퍼)라고 하는 Direct3D 자료구조 안에 담겨서 렌더링 파이프라인에 묶이는데

이 VertexBuffer자체에는 기본도형(점, 선, 면)을 형성하기 위해서 정점들을 조합하는 방법에 대한 정보가 없다.

그래서 이 도형을 형성하는 방법을 알려주기 위해서 기본도형 위상구조(primitive topology)라는것을 설정해야 하는데

이때 IASetPrimitiveTopology 메서드를 이용하고 D3D_PRIMITIVE_TOPOLOGY 형의 enum을 넘겨주면 된다.

 

여기서 넘겨주는 D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST는 삼각형 목록을 적용해서 물체들을 그린다는 의미이다.

 

다음라인의 IASetVertexBuffers는 VertexBuffer를 파이프라인의 입력슬롯에 묶어주는 메서드이다.

첫번째 인자는 시작슬롯이다. 첫째 정점 버퍼를 묶을 입력 슬롯의 index이다. (입력 슬롯은 총 16개 0~15)

두번째 인자는 입력 슬롯들에 묶을 정점 버퍼의 갯수이다. 

세번째 인자는 VertexBufferVIew 배열의 첫 원소를 가리키는 포인터이다.

 

0번째 입력슬롯에 1개의 버퍼를 묶고 VertexBufferView의 주소를 넘겨주었다.

 

다음 메서드인 DrawInstanced는 인덱싱되지 않고 인스턴스화 된 프리미티브를 그린다.

여기서는 삼각형만 찍고 삼각형을 찍기 위해서 VertexBuffer들만 사용하였는데 나중에 좀더 복잡한 도형을 그리게 되면은

Vertex들의 중복으로 인해서 그리고자 하는 도형의 Vertex의 중복을 막고 어떤 Vertex들을 이어서 도형을 만들어나갈지 적어놓은

IndexBuffer라는것이 있다. 이 IndexBuffer들을 이용하려면 DrawIndexedInstanced 메서드를 사용해야 한다.

 

지금은 VertexBuffer만 있고 Vertex 3개(정점 3개)로 삼각형 하나를 띄우는 것이므로

DrawInstanced를 이용해서 그리면 된다.

첫번째 변수는 그림 정점들의 갯수이다.(인스턴스당) - 여기서는 삼각형을 그릴 3개의 Vertex(정점) 이므로 3.

두번째 변수는 그릴 인스턴스의 갯수이다.

세번째 변수는 정점 버퍼에서 이 그리기 호출로 그릴 일련의 정점들 중 첫 정점의 index....

                   최초의 정점 인덱스

네번째 변수는 버텍스 버퍼에서 인스턴스 당 데이터를 읽기 전에 각 인덱스에 추가 된 값.

 

이제 BackBuffer에 삼각형을 그렸다.

 

다음 코드를 보자.

 

	// Indicate that the back buffer will now be used to present.
	m_commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_renderTargets[m_frameIndex].Get(), 
										D3D12_RESOURCE_STATE_RENDER_TARGET, 
										D3D12_RESOURCE_STATE_PRESENT));

	ThrowIfFailed(m_commandList->Close());

 

다 그린 후에 위에서 상태를 변경한것처럼 이번에도 상태를 변경해주어야 한다.(그리는 작업이 끝났으므로...)

백버퍼에 기록한것을 화면으로 전환하는 상태로 변경하고

 

명령들을 모두 끝냈으므로 Close로 닫아준다.

 

이제 PopulateCommandList 메서드가 끝나고 다음 라인을 보면

 

	// Execute the command list.
	ID3D12CommandList* ppCommandLists[] = { m_commandList.Get() };
	m_commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);

	// Present the frame.
	ThrowIfFailed(m_swapChain->Present(1, 0));

	WaitForPreviousFrame();

 

저번 프로젝트랑 다른점이 없다.

 

주석대로 commandList를 실행하고 Present로 프레임 전환하고

WaitForPreviousFrame 메서드를 실행한다.

 

 

void D3D12HelloTriangle::WaitForPreviousFrame()
{
	// WAITING FOR THE FRAME TO COMPLETE BEFORE CONTINUING IS NOT BEST PRACTICE.
	// This is code implemented as such for simplicity. The D3D12HelloFrameBuffering
	// sample illustrates how to use fences for efficient resource usage and to
	// maximize GPU utilization.

	// Signal and increment the fence value.
	const UINT64 fence = m_fenceValue;
	ThrowIfFailed(m_commandQueue->Signal(m_fence.Get(), fence));
	m_fenceValue++;

	// Wait until the previous frame is finished.
	if (m_fence->GetCompletedValue() < fence)
	{
		ThrowIfFailed(m_fence->SetEventOnCompletion(fence, m_fenceEvent));
		WaitForSingleObject(m_fenceEvent, INFINITE);
	}

	m_frameIndex = m_swapChain->GetCurrentBackBufferIndex();
}

 

저번 프로젝트와 달라진 코드는 없다.

 

이것으로 OnUpdate와 OnRender가 끝났다.

마지막으로 OnDestroy가 남았는데 짧은 코드니까 마저 보자.

 

 

void D3D12HelloTriangle::OnDestroy()
{
	// Ensure that the GPU is no longer referencing resources that are about to be
	// cleaned up by the destructor.
	WaitForPreviousFrame();

	CloseHandle(m_fenceEvent);
}

 

주석을 번역하면 "GPU가 더 이상 소멸자가 정리하려고하는 리소스를 참조하지 않도록하십시오." 라고 한다.

그래서 WaitForPreviousFrame을 호출하고 CloseHandle을 통해서 울타리이벤트 핸들을 닫고 있다.

 

 

이로써 HelloTriangle이 끝이났다. 

다음에는 HelloTexture를 진행해 보도록 해야겠다 

 

 

끗.

 

 

반응형