MS에서 지원하는 DirectX Tool Kit인 DirectXTK12를 적용해서 예시 파일에서 사용하던 d3dx12를 적용했습니다.

처음에는 Nuget Package Manager로 쓰면 되겠거니 했는데 막상 해보니까

다른건 다 있으면서 정작 d3dx12는 선언이 되어있지 않았습니다.

코드도 미묘하게 달랐습니다.

Github에 올라온 코드는 d3dx12에서 선언한 하위 Structure를 사용했지만,

Nuget에 올라온 Package는 이를 사용하지 않았습니다.

물론 이를 하나하나 체크해볼 수도 있었겠지만 일단은 Submodule로 Github Repository를 붙였습니다.

이제 Library를 가져다 쓰는 것을 좀 찾아봐서 깔끔하게 이어 붙이고,

중복된 코드를 삭제해서 필요 없는 코드를 모두 제거하려 합니다.

 

DirectX가 아니라 VS를 다루는 과정이지만 깔끔하게 정리를 하고 다음 과정을 진행하겠습니다.

'내용정리 > DirectX12' 카테고리의 다른 글

10. Box Example 3 - BoxApp, WinMain  (0) 2020.09.15
10. Box Example 2 - D3DApp  (0) 2020.09.08
10. Box Example 1  (0) 2020.09.04
20.08.25 개발일지  (0) 2020.08.25
20.08.21 개발일지  (0) 2020.08.21

Box Example의 마지막. BoxApp과 WinMain 함수를 살펴보도록 합시다.

더보기
#pragma once
#include "../Common/D3DApp.h"
#include "../Common/UploadBuffer.h"
#include "../Common/MeshGeometry.h"

using Microsoft::WRL::ComPtr;
using namespace DirectX;
using namespace DirectX::PackedVector;

struct Vertex
{
	XMFLOAT3 Pos;
	XMFLOAT4 Color;
};

struct ObjectConstants
{
	XMFLOAT4X4 WorldViewProj{
		1.f, 0.f, 0.f, 0.f,
		0.f, 1.f, 0.f, 0.f,
		0.f, 0.f, 1.f, 0.f,
		0.f, 0.f, 0.f, 1.f
	};
};

class BoxApp : public D3DApp
{
public:
	BoxApp(HINSTANCE hInstance);
	BoxApp(const BoxApp& rhs) = delete;
	BoxApp& operator=(const BoxApp& rhs) = delete;
	virtual ~BoxApp();

	virtual bool Initialize() override;

private:
	virtual void OnResize() override;
	virtual void Update(const GameTimer& gt) override;
	virtual void Draw(const GameTimer& gt) override;

	virtual void OnMouseDown(WPARAM btnState, int x, int y) override;
	virtual void OnMouseUp(WPARAM btnState, int x, int y) override;
	virtual void OnMouseMove(WPARAM btnState, int x, int y) override;

	void BuildDescriptorHeaps();
	void BuildConstantBuffers();
	void BuildRootSignature();
	void BuildShadersAndInputLayout();
	void BuildBoxGeometry();
	void BuildPSO();

private:
	ComPtr<ID3D12RootSignature> mRootSignature = nullptr;
	ComPtr<ID3D12DescriptorHeap> mCbvHeap = nullptr;

	std::unique_ptr<UploadBuffer<ObjectConstants>> mObjectCB = nullptr;
	std::unique_ptr<MeshGeometry> mBoxGeo = nullptr;

	ComPtr<ID3DBlob> mvsByteCode = nullptr;
	ComPtr<ID3DBlob> mpsByteCode = nullptr;

	std::vector<D3D12_INPUT_ELEMENT_DESC> mInputLayout;

	ComPtr<ID3D12PipelineState> mPSO = nullptr;

	XMFLOAT4X4 mWorld{
		1.f, 0.f, 0.f, 0.f,
		0.f, 1.f, 0.f, 0.f,
		0.f, 0.f, 1.f, 0.f,
		0.f, 0.f, 0.f, 1.f
	};
	XMFLOAT4X4 mView{
		1.f, 0.f, 0.f, 0.f,
		0.f, 1.f, 0.f, 0.f,
		0.f, 0.f, 1.f, 0.f,
		0.f, 0.f, 0.f, 1.f
	};
	XMFLOAT4X4 mProj{
		1.f, 0.f, 0.f, 0.f,
		0.f, 1.f, 0.f, 0.f,
		0.f, 0.f, 1.f, 0.f,
		0.f, 0.f, 0.f, 1.f
	};

	float mTheta = 1.5f * XM_PI;
	float mPhi = XM_PIDIV4;
	float mRadius = 5.f;

	POINT mLastMousePos;
};

헤더 부분을 넘어가기 전에 선언된 Struct들을 보고 가자.

 

struct Vertex
{
	XMFLOAT3 Pos;
	XMFLOAT4 Color;
};

struct ObjectConstants
{
	XMFLOAT4X4 WorldViewProj{
		1.f, 0.f, 0.f, 0.f,
		0.f, 1.f, 0.f, 0.f,
		0.f, 0.f, 1.f, 0.f,
		0.f, 0.f, 0.f, 1.f
	};
};

이 예제에서 사용되는 Vertex 정보와 선언된 WorldViewProj이다.

특별한 것은 아니지만 선언 된 것이니 이를 기억해두고 가면 될 것 같다.

 

함수 선언부를 하나하나 살펴보겠습니다.

public:
	BoxApp(HINSTANCE hInstance);
	BoxApp(const BoxApp& rhs) = delete;
	BoxApp& operator=(const BoxApp& rhs) = delete;
	virtual ~BoxApp();

생성자와 소멸자는 D3DApp과 마찬가지로 Singleton을 구현한 형태입니다.

생성자에 virtual 선언이 없지만 구현부를 보면 Initializer로 호출을 해놓은 상태입니다.

 

다음은 기능적인 부분입니다.

virtual bool Initialize() override;

private:
	virtual void OnResize() override;
	virtual void Update(const GameTimer& gt) override;
	virtual void Draw(const GameTimer& gt) override;

	virtual void OnMouseDown(WPARAM btnState, int x, int y) override;
	virtual void OnMouseUp(WPARAM btnState, int x, int y) override;
	virtual void OnMouseMove(WPARAM btnState, int x, int y) override;

D3DApp에서 기본적인 부분만 선언 되어 있거나, 선언되어 있지 않은 함수들의 override가 선언되어 있습니다.

이 형태는 앞으로도 자주 보일 것입니다.

 

다음은 렌더링 부분입니다.

void BuildDescriptorHeaps();
void BuildConstantBuffers();
void BuildRootSignature();
void BuildShadersAndInputLayout();
void BuildBoxGeometry();
void BuildPSO();

DescriptorHeaps, ConstantBuffer 등 렌더링을 하면서 필요한 것들을 선언하고 초기화 하는 함수들입니다.

이 부분은 현재 예시 별로 코드가 분리되어 있지만, 나중에 하나로 합칠 여지가 있다면 합쳐볼 계획입니다.

 

마지막으로 Field들입니다.

private:
	ComPtr<ID3D12RootSignature> mRootSignature = nullptr;
	ComPtr<ID3D12DescriptorHeap> mCbvHeap = nullptr;

	std::unique_ptr<UploadBuffer<ObjectConstants>> mObjectCB = nullptr;
	std::unique_ptr<MeshGeometry> mBoxGeo = nullptr;

	ComPtr<ID3DBlob> mvsByteCode = nullptr;
	ComPtr<ID3DBlob> mpsByteCode = nullptr;

	std::vector<D3D12_INPUT_ELEMENT_DESC> mInputLayout;

	ComPtr<ID3D12PipelineState> mPSO = nullptr;

	XMFLOAT4X4 mWorld{
		1.f, 0.f, 0.f, 0.f,
		0.f, 1.f, 0.f, 0.f,
		0.f, 0.f, 1.f, 0.f,
		0.f, 0.f, 0.f, 1.f
	};
	XMFLOAT4X4 mView{
		1.f, 0.f, 0.f, 0.f,
		0.f, 1.f, 0.f, 0.f,
		0.f, 0.f, 1.f, 0.f,
		0.f, 0.f, 0.f, 1.f
	};
	XMFLOAT4X4 mProj{
		1.f, 0.f, 0.f, 0.f,
		0.f, 1.f, 0.f, 0.f,
		0.f, 0.f, 1.f, 0.f,
		0.f, 0.f, 0.f, 1.f
	};

	float mTheta = 1.5f * XM_PI;
	float mPhi = XM_PIDIV4;
	float mRadius = 5.f;

	POINT mLastMousePos;

 

대부분은 이름이나 내용을 보면 이해가 될 것이고, 몇가지만 짚고 넘어가겠습니다.

mObjectCB는 ObjectConstantBuffer의 약자입니다.

mCbvHeap에서 m은 변수 앞에 동일하게 붙는 문자고, Cbv는 Constant Buffer View의 약자입니다.

mvsByteCode, mpsByteCode에서도 앞의 m은 제외하고 봐야 합니다.

vs는 Vertex Shader, ps는 Pixel Shader의 약자입니다.

PSO는 Pipeline State Object의 약자입니다.

 

DirectX 코드를 보면서 가장 불편한 점은 Type을 통해 기능을 명확히 알 수 없다는 점입니다.

결국 무엇인지 알기 위해서는 변수명을 보고 판단해야 하는데, 초보자에게는 약자는 너무 어려운 암호입니다.

이 글을 보고 그래도 대략적인 감은 잡고 가셨으면 좋겠습니다.

 

이제 구현부를 살펴봅시다.

더보기
#include "BoxApp.h"

BoxApp::BoxApp(HINSTANCE hInstance) : D3DApp(hInstance), mLastMousePos(POINT{ 0, 0 })
{
}

BoxApp::~BoxApp()
{
}

bool BoxApp::Initialize()
{
	if (!D3DApp::Initialize())
	{
		return false;
	}

	ThrowIfFailed(mCommandList->Reset(mDirectCmdListAlloc.Get(), nullptr));
	BuildDescriptorHeaps();
	BuildConstantBuffers();
	BuildRootSignature();
	BuildShadersAndInputLayout();
	BuildBoxGeometry();
	BuildPSO();

	ThrowIfFailed(mCommandList->Close());
	ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };
	mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);

	FlushCommandQueue();
	return true;
}

void BoxApp::OnResize()
{
	D3DApp::OnResize();

	XMMATRIX P = XMMatrixPerspectiveFovLH(XM_PIDIV4, AspectRatio(), 1.0f, 1000.0f);
	XMStoreFloat4x4(&mProj, P);
}

void BoxApp::Update(const GameTimer& gt)
{
	float x = mRadius * sinf(mPhi) * cosf(mTheta);
	float z = mRadius * sinf(mPhi) * sinf(mTheta);
	float y = mRadius * cosf(mPhi);

	XMVECTOR pos = XMVectorSet(x, y, z, 1.0f);
	XMVECTOR target = XMVectorZero();
	XMVECTOR up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);

	XMMATRIX view = XMMatrixLookAtLH(pos, target, up);
	XMStoreFloat4x4(&mView, view);

	XMMATRIX world = XMLoadFloat4x4(&mWorld);
	XMMATRIX proj = XMLoadFloat4x4(&mProj);
	XMMATRIX worldViewProj = world * view * proj;

	ObjectConstants objConstants;
	XMStoreFloat4x4(&objConstants.WorldViewProj, XMMatrixTranspose(worldViewProj));
	mObjectCB->CopyData(0, objConstants);
}

void BoxApp::Draw(const GameTimer& gt)
{
	ThrowIfFailed(mDirectCmdListAlloc->Reset());
	ThrowIfFailed(mCommandList->Reset(mDirectCmdListAlloc.Get(), mPSO.Get()));

	// Set CommandList
	mCommandList->RSSetViewports(1, &mScreenViewport);
	mCommandList->RSSetScissorRects(1, &mScissorRect);

	// Set CommandList - not problem
	D3D12_RESOURCE_BARRIER BeforeBarrier;
	ZeroMemory(&BeforeBarrier, sizeof(BeforeBarrier));
	BeforeBarrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
	BeforeBarrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
	BeforeBarrier.Transition.pResource = CurrentBackBuffer();
	BeforeBarrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
	BeforeBarrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
	BeforeBarrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
	

	mCommandList->ResourceBarrier(1, &BeforeBarrier);

	mCommandList->ClearRenderTargetView(CurrentBackBufferView(), Colors::LightSteelBlue, 0, nullptr);
	mCommandList->ClearDepthStencilView(DepthStencilView(), D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr);

	// Specify the buffers we are going to render to.
	// Set CommandList
	mCommandList->OMSetRenderTargets(1, &CurrentBackBufferView(), true, &DepthStencilView());

	ID3D12DescriptorHeap* descriptorHeaps[] = { mCbvHeap.Get() };
	mCommandList->SetDescriptorHeaps(_countof(descriptorHeaps), descriptorHeaps);

	// Set CommandList
	mCommandList->SetGraphicsRootSignature(mRootSignature.Get());

	// Set CommandList
	mCommandList->IASetVertexBuffers(0, 1, &mBoxGeo->VertexBufferView());
	mCommandList->IASetIndexBuffer(&mBoxGeo->IndexBufferView());
	mCommandList->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

	// Set CommandList
	mCommandList->SetGraphicsRootDescriptorTable(0, mCbvHeap->GetGPUDescriptorHandleForHeapStart());

	mCommandList->DrawIndexedInstanced(mBoxGeo->DrawArgs["box"].IndexCount, 1, 0, 0, 0);

	// Set CommandList - not problem
	D3D12_RESOURCE_BARRIER AfterBarrier;
	ZeroMemory(&AfterBarrier, sizeof(AfterBarrier));
	AfterBarrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
	AfterBarrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
	AfterBarrier.Transition.pResource = CurrentBackBuffer();
	AfterBarrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
	AfterBarrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
	AfterBarrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;

	mCommandList->ResourceBarrier(1, &AfterBarrier);
	ThrowIfFailed(mCommandList->Close());

	ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };
	mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);

	ThrowIfFailed(mSwapChain->Present(0, 0));
	mCurrentBackBuffer = (mCurrentBackBuffer + 1) % SwapChainBufferCount;

	FlushCommandQueue();
}

void BoxApp::OnMouseDown(WPARAM btnState, int x, int y)
{
	mLastMousePos.x = x;
	mLastMousePos.y = y;
	SetCapture(mhMainWnd);
}

void BoxApp::OnMouseUp(WPARAM btnState, int x, int y)
{
	ReleaseCapture();
}

void BoxApp::OnMouseMove(WPARAM btnState, int x, int y)
{
	if ((btnState & MK_LBUTTON) != 0)
	{
		float dx = XMConvertToRadians(0.25f * static_cast<float>(x - mLastMousePos.x));
		float dy = XMConvertToRadians(0.25f * static_cast<float>(y - mLastMousePos.y));

		mTheta += dx;
		mPhi += dy;

		mPhi = Clamp(mPhi, 0.1f, XM_PI - 0.1f);
	}
	else if ((btnState && MK_RBUTTON) != 0)
	{
		float dx = 0.005f * static_cast<float>(x - mLastMousePos.x);
		float dy = 0.005f * static_cast<float>(y - mLastMousePos.y);

		mRadius += dx - dy;

		mRadius = Clamp(mRadius, 3.f, 15.f);
	}
	mLastMousePos.x = x;
	mLastMousePos.y = y;
}

void BoxApp::BuildDescriptorHeaps()
{
	D3D12_DESCRIPTOR_HEAP_DESC cbvHeapDesc;
	cbvHeapDesc.NumDescriptors = 1;
	cbvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
	cbvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
	cbvHeapDesc.NodeMask = 0;
	ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&cbvHeapDesc, IID_PPV_ARGS(&mCbvHeap)));
}

void BoxApp::BuildConstantBuffers()
{
	mObjectCB = std::make_unique<UploadBuffer<ObjectConstants>>(md3dDevice.Get(), 1, true);

	UINT objCBByteSize = calcConstantBufferByteSize(sizeof(ObjectConstants));

	D3D12_GPU_VIRTUAL_ADDRESS cbAddress = mObjectCB->Resource()->GetGPUVirtualAddress();
	
	int boxCBufIndex = 0;
	cbAddress += static_cast<UINT64>(boxCBufIndex) * static_cast<UINT64>(objCBByteSize);
	
	D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc;
	cbvDesc.BufferLocation = cbAddress;
	cbvDesc.SizeInBytes = calcConstantBufferByteSize(sizeof(ObjectConstants));

	md3dDevice->CreateConstantBufferView(&cbvDesc, mCbvHeap->GetCPUDescriptorHandleForHeapStart());
}

void BoxApp::BuildRootSignature()
{
	D3D12_ROOT_PARAMETER slotRootParameter[1];

	D3D12_DESCRIPTOR_RANGE cbvTable;
	cbvTable.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_CBV;
	cbvTable.NumDescriptors = 1;
	cbvTable.BaseShaderRegister = 0;
	cbvTable.RegisterSpace = 0;
	cbvTable.OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
	
	slotRootParameter[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
	slotRootParameter[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
	slotRootParameter[0].DescriptorTable.NumDescriptorRanges = 1;
	slotRootParameter[0].DescriptorTable.pDescriptorRanges = &cbvTable;

	D3D12_ROOT_SIGNATURE_DESC rootSigDesc{ 1, slotRootParameter, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT };

	ComPtr<ID3DBlob> serializedRootSig = nullptr;
	ComPtr<ID3DBlob> errorBlob = nullptr;
	HRESULT hr = D3D12SerializeRootSignature(&rootSigDesc, D3D_ROOT_SIGNATURE_VERSION_1, serializedRootSig.GetAddressOf(), errorBlob.GetAddressOf());

	if (errorBlob != nullptr)
	{
		::OutputDebugStringA(static_cast<char*>(errorBlob->GetBufferPointer()));
	}
	ThrowIfFailed(hr);

	ThrowIfFailed(md3dDevice->CreateRootSignature(
		0, 
		serializedRootSig->GetBufferPointer(), 
		serializedRootSig->GetBufferSize(), 
		IID_PPV_ARGS(&mRootSignature)));

}

void BoxApp::BuildShadersAndInputLayout()
{
	HRESULT hr = S_OK;
	
	mvsByteCode = CompileShader(L"Shaders\\Ch06\\color.hlsl", nullptr, "VS", "vs_5_0");
	mpsByteCode = CompileShader(L"Shaders\\Ch06\\color.hlsl", nullptr, "PS", "ps_5_0");

	mInputLayout =
	{
		{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
		{"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}
	};
}

void BoxApp::BuildBoxGeometry()
{
	std::array<Vertex, 8> vertices
	{
		Vertex({XMFLOAT3(-1.f, -1.f, -1.f), XMFLOAT4(Colors::White)}), 
		Vertex({XMFLOAT3(-1.f, 1.f, -1.f), XMFLOAT4(Colors::Black)}),
		Vertex({XMFLOAT3(1.f, 1.f, -1.f), XMFLOAT4(Colors::Red)}),
		Vertex({XMFLOAT3(1.f, -1.f, -1.f), XMFLOAT4(Colors::Green)}),
		Vertex({XMFLOAT3(-1.f, -1.f, 1.f), XMFLOAT4(Colors::Blue)}),
		Vertex({XMFLOAT3(-1.f, 1.f, 1.f), XMFLOAT4(Colors::Yellow)}),
		Vertex({XMFLOAT3(1.f, 1.f, 1.f), XMFLOAT4(Colors::Cyan)}),
		Vertex({XMFLOAT3(1.f, -1.f, 1.f), XMFLOAT4(Colors::Magenta)}),
	};

	std::array<std::uint16_t, 36> indices =
	{
		0, 1, 2, 
		0, 2, 3, 
		
		4, 6, 5, 
		4, 7, 6, 
		
		4, 5, 1, 
		4, 1, 0, 
		
		3, 2, 6, 
		3, 6, 7, 
		
		1, 5, 6, 
		1, 6, 2,

		4, 0, 3, 
		4, 3, 7
	};

	const UINT vbByteSize = static_cast<UINT>(vertices.size()) * sizeof(Vertex);
	const UINT ibByteSize = static_cast<UINT>(indices.size()) * sizeof(std::uint16_t);

	mBoxGeo = std::make_unique<MeshGeometry>();
	mBoxGeo->Name = "boxGeo";

	ThrowIfFailed(D3DCreateBlob(vbByteSize, &mBoxGeo->VertexBufferCPU));
	CopyMemory(mBoxGeo->VertexBufferCPU->GetBufferPointer(), vertices.data(), vbByteSize);

	ThrowIfFailed(D3DCreateBlob(ibByteSize, &mBoxGeo->IndexBufferCPU));
	CopyMemory(mBoxGeo->IndexBufferCPU->GetBufferPointer(), indices.data(), ibByteSize);

	mBoxGeo->VertexBufferGPU = CreateDefaultbuffer(md3dDevice.Get(), mCommandList.Get(), vertices.data(), vbByteSize, mBoxGeo->VertexBufferUploader);
	mBoxGeo->IndexBufferGPU = CreateDefaultbuffer(md3dDevice.Get(), mCommandList.Get(), indices.data(), ibByteSize, mBoxGeo->IndexBufferUploader);

	mBoxGeo->VertexByteStride = sizeof(Vertex);
	mBoxGeo->VertexBufferByteSize = vbByteSize;
	mBoxGeo->IndexFormat = DXGI_FORMAT_R16_UINT;
	mBoxGeo->IndexBufferByteSize = ibByteSize;

	SubmeshGeometry submesh;
	submesh.IndexCount = static_cast<UINT>(indices.size());
	submesh.StartIndexLocation = 0;
	submesh.BaseVertexLocation = 0;

	mBoxGeo->DrawArgs["box"] = submesh;
}

void BoxApp::BuildPSO()
{
	D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc;
	ZeroMemory(&psoDesc, sizeof(D3D12_GRAPHICS_PIPELINE_STATE_DESC));
	psoDesc.InputLayout = D3D12_INPUT_LAYOUT_DESC{ mInputLayout.data(), static_cast<UINT>(mInputLayout.size()) };
	psoDesc.pRootSignature = mRootSignature.Get();
	psoDesc.VS =
	{
		reinterpret_cast<BYTE*>(mvsByteCode->GetBufferPointer()),
		mvsByteCode->GetBufferSize()
	};
	psoDesc.PS =
	{
		reinterpret_cast<BYTE*>(mpsByteCode->GetBufferPointer()),
		mpsByteCode->GetBufferSize()
	};
	
	D3D12_RASTERIZER_DESC Rasterizer;
	Rasterizer.FillMode = D3D12_FILL_MODE_SOLID;
	Rasterizer.CullMode = D3D12_CULL_MODE_BACK;
	Rasterizer.FrontCounterClockwise = FALSE;
	Rasterizer.DepthBias = D3D12_DEFAULT_DEPTH_BIAS;
	Rasterizer.DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP;
	Rasterizer.SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS;
	Rasterizer.DepthClipEnable = TRUE;
	Rasterizer.MultisampleEnable = FALSE;
	Rasterizer.AntialiasedLineEnable = FALSE;
	Rasterizer.ForcedSampleCount = 0;
	Rasterizer.ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF;
	
	D3D12_BLEND_DESC Blend;
	Blend.AlphaToCoverageEnable = FALSE;
	Blend.IndependentBlendEnable = FALSE;
	const D3D12_RENDER_TARGET_BLEND_DESC defaultRenderTargetBlendDesc
	{
		FALSE, FALSE, 
		D3D12_BLEND_ONE, D3D12_BLEND_ZERO, D3D12_BLEND_OP_ADD, 
		D3D12_BLEND_ONE, D3D12_BLEND_ZERO, D3D12_BLEND_OP_ADD, 
		D3D12_LOGIC_OP_NOOP, 
		D3D12_COLOR_WRITE_ENABLE_ALL,
	};
	for (auto& iter : Blend.RenderTarget)
	{
		iter = defaultRenderTargetBlendDesc;
	}

	D3D12_DEPTH_STENCIL_DESC DepthStencil;
	DepthStencil.DepthEnable = TRUE;
	DepthStencil.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL;
	DepthStencil.DepthFunc = D3D12_COMPARISON_FUNC_LESS;
	DepthStencil.StencilEnable = FALSE;
	DepthStencil.StencilReadMask = D3D12_DEFAULT_STENCIL_READ_MASK;
	DepthStencil.StencilWriteMask = D3D12_DEFAULT_STENCIL_WRITE_MASK;
	const D3D12_DEPTH_STENCILOP_DESC defaultStencilOp = 
	{
		D3D12_STENCIL_OP_KEEP, 
		D3D12_STENCIL_OP_KEEP,
		D3D12_STENCIL_OP_KEEP,
		D3D12_COMPARISON_FUNC_ALWAYS
	};
	DepthStencil.FrontFace = defaultStencilOp;
	DepthStencil.BackFace = defaultStencilOp;

	psoDesc.RasterizerState = Rasterizer;
	psoDesc.BlendState = Blend;
	psoDesc.DepthStencilState = DepthStencil;
	psoDesc.SampleMask = UINT_MAX;
	psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
	psoDesc.NumRenderTargets = 1;
	psoDesc.RTVFormats[0] = mBackBufferFormat;
	psoDesc.SampleDesc.Count = m4xMsaaState ? 4 : 1;
	psoDesc.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
	psoDesc.DSVFormat = mDepthStencilFormat;
	ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&mPSO)));
}

 

생성자와 소멸자 부분은 별 내용이 없으므로 설명을 생략하겠습니다.

BoxApp::BoxApp(HINSTANCE hInstance) : D3DApp(hInstance), mLastMousePos(POINT{ 0, 0 })
{
}

BoxApp::~BoxApp()
{
}

 

다음은 Initializer입니다.

bool BoxApp::Initialize()
{
	if (!D3DApp::Initialize())
	{
		return false;
	}

	ThrowIfFailed(mCommandList->Reset(mDirectCmdListAlloc.Get(), nullptr));
	BuildDescriptorHeaps();
	BuildConstantBuffers();
	BuildRootSignature();
	BuildShadersAndInputLayout();
	BuildBoxGeometry();
	BuildPSO();

	ThrowIfFailed(mCommandList->Close());
	ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };
	mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);

	FlushCommandQueue();
	return true;
}

상위 class인 D3DApp의 Initialize()가 완료가 되었다면, Box를 렌더링 할 함수들을 호출합니다.

이에 앞서 CommandList를 초기화 하고, Private로 선언된 모든 Build 함수들을 호출합니다.

Build가 완료가 되면 CommandList를 닫아주고,

CommandList 안의 내용을 복사하여 CommandQueue에 넣어주고 이를 실행합니다.

내용이 실행이 된 이후에는 CommandQueue를 초기화 하고,

이 과정이 마무리 되면 모든 기능이 정상 작동 되었기에 true를 반환합니다.

 

뒷 함수들에 앞서 그럼 Build 함수들을 먼저 살펴보겠습니다.

가장 먼저 볼 것은 BuildDescriptorHeaps입니다.

void BoxApp::BuildDescriptorHeaps()
{
	D3D12_DESCRIPTOR_HEAP_DESC cbvHeapDesc;
	cbvHeapDesc.NumDescriptors = 1;
	cbvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
	cbvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
	cbvHeapDesc.NodeMask = 0;
	ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&cbvHeapDesc, IID_PPV_ARGS(&mCbvHeap)));
}

앞에 거창하게 소개 했지만 사실 큰 내용은 없습니다.

DescriptorHeaps에서 사용하는 구조체인 D3D12_DESCRIPTOR_HEAP_DESC 안에 내용을 채워 넣고 생성해주면 됩니다.

 

다음은 BuildConstantBuffers입니다.

void BoxApp::BuildConstantBuffers()
{
	mObjectCB = std::make_unique<UploadBuffer<ObjectConstants>>(md3dDevice.Get(), 1, true);

	UINT objCBByteSize = calcConstantBufferByteSize(sizeof(ObjectConstants));

	D3D12_GPU_VIRTUAL_ADDRESS cbAddress = mObjectCB->Resource()->GetGPUVirtualAddress();
	
	int boxCBufIndex = 0;
	cbAddress += static_cast<UINT64>(boxCBufIndex) * static_cast<UINT64>(objCBByteSize);
	
	D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc;
	cbvDesc.BufferLocation = cbAddress;
	cbvDesc.SizeInBytes = calcConstantBufferByteSize(sizeof(ObjectConstants));

	md3dDevice->CreateConstantBufferView(&cbvDesc, mCbvHeap->GetCPUDescriptorHandleForHeapStart());
}

UploadBuffer의 사이즈와 ConstantBuffer의 ByteSize를 연산해 GPUVirtualAddress에 그 크기만큼 공간을 잡아줍니다.

그리고 ConstantBufferView의 시작 위치와 크기를 위에서 연산한 것들로 넣어 줘 ConstantBufferView를 생성합니다.

 

다음은 BuildRootSignature입니다. 이번건 길이가 좀 됩니다.

void BoxApp::BuildRootSignature()
{
	D3D12_ROOT_PARAMETER slotRootParameter[1];

	D3D12_DESCRIPTOR_RANGE cbvTable;
	cbvTable.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_CBV;
	cbvTable.NumDescriptors = 1;
	cbvTable.BaseShaderRegister = 0;
	cbvTable.RegisterSpace = 0;
	cbvTable.OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
	
	slotRootParameter[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
	slotRootParameter[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
	slotRootParameter[0].DescriptorTable.NumDescriptorRanges = 1;
	slotRootParameter[0].DescriptorTable.pDescriptorRanges = &cbvTable;

	D3D12_ROOT_SIGNATURE_DESC rootSigDesc{ 1, slotRootParameter, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT };

	ComPtr<ID3DBlob> serializedRootSig = nullptr;
	ComPtr<ID3DBlob> errorBlob = nullptr;
	HRESULT hr = D3D12SerializeRootSignature(&rootSigDesc, D3D_ROOT_SIGNATURE_VERSION_1, serializedRootSig.GetAddressOf(), errorBlob.GetAddressOf());

	if (errorBlob != nullptr)
	{
		::OutputDebugStringA(static_cast<char*>(errorBlob->GetBufferPointer()));
	}
	ThrowIfFailed(hr);

	ThrowIfFailed(md3dDevice->CreateRootSignature(
		0, 
		serializedRootSig->GetBufferPointer(), 
		serializedRootSig->GetBufferSize(), 
		IID_PPV_ARGS(&mRootSignature)));

}

이 과정은 Shader를 Application에 알맞게 작성 되었는지 확인하고 허가 받는 과정입니다.

때문에 반드시 Shader와 호환이 되어야 합니다.

만약 Shader Program이 달라진다면, Root Signature도 달라지게 됩니다.

 

자세한 내용은 아래 링크를 참조하면 될 것 같습니다.

ssinyoung.tistory.com/41

 

DirectX 12 루트 서명(Root Signature)과 파이프라인 상태(Pipeline State)

[ 루트 서명(Root Signature) ] 루트 서명(Root Signature)란 어떤 리소스(데이터)들이 그래픽스 파이프라인의 셰이더에 연결되는 가를 정의하는 것이다. 즉, 단어 그대로 작성한 셰이더를 응용 프로그램��

ssinyoung.tistory.com

docs.microsoft.com/ko-kr/windows/win32/direct3d12/root-signatures

 

루트 서명 - Win32 apps

루트 서명은 그래픽 파이프라인에 바인딩된 리소스 종류를 정의합니다.

docs.microsoft.com

다음은 BuildShadersAndInputLayout입니다.

void BoxApp::BuildShadersAndInputLayout()
{
	HRESULT hr = S_OK;
	
	mvsByteCode = CompileShader(L"Shaders\\Ch06\\color.hlsl", nullptr, "VS", "vs_5_0");
	mpsByteCode = CompileShader(L"Shaders\\Ch06\\color.hlsl", nullptr, "PS", "ps_5_0");

	mInputLayout =
	{
		{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
		{"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}
	};
}

이는 저장되어 있는 Shader 코드를 읽어오는 함수이다.

나중에 팀 내에서 사용하는 방식에 따라 달라지겠지만, 적어도 이 프로젝트에서는 그 형태가 변할 이유는 없다.

 

이 함수는 저장된 Vertex와 Pixel들을 이용해 Box Shader를 만드는 BuildBoxGeometry이다.

void BoxApp::BuildBoxGeometry()
{
	std::array<Vertex, 8> vertices
	{
		Vertex({XMFLOAT3(-1.f, -1.f, -1.f), XMFLOAT4(Colors::White)}), 
		Vertex({XMFLOAT3(-1.f, 1.f, -1.f), XMFLOAT4(Colors::Black)}),
		Vertex({XMFLOAT3(1.f, 1.f, -1.f), XMFLOAT4(Colors::Red)}),
		Vertex({XMFLOAT3(1.f, -1.f, -1.f), XMFLOAT4(Colors::Green)}),
		Vertex({XMFLOAT3(-1.f, -1.f, 1.f), XMFLOAT4(Colors::Blue)}),
		Vertex({XMFLOAT3(-1.f, 1.f, 1.f), XMFLOAT4(Colors::Yellow)}),
		Vertex({XMFLOAT3(1.f, 1.f, 1.f), XMFLOAT4(Colors::Cyan)}),
		Vertex({XMFLOAT3(1.f, -1.f, 1.f), XMFLOAT4(Colors::Magenta)}),
	};

	std::array<std::uint16_t, 36> indices =
	{
		0, 1, 2, 
		0, 2, 3, 
		
		4, 6, 5, 
		4, 7, 6, 
		
		4, 5, 1, 
		4, 1, 0, 
		
		3, 2, 6, 
		3, 6, 7, 
		
		1, 5, 6, 
		1, 6, 2,

		4, 0, 3, 
		4, 3, 7
	};

	const UINT vbByteSize = static_cast<UINT>(vertices.size()) * sizeof(Vertex);
	const UINT ibByteSize = static_cast<UINT>(indices.size()) * sizeof(std::uint16_t);

	mBoxGeo = std::make_unique<MeshGeometry>();
	mBoxGeo->Name = "boxGeo";

	ThrowIfFailed(D3DCreateBlob(vbByteSize, &mBoxGeo->VertexBufferCPU));
	CopyMemory(mBoxGeo->VertexBufferCPU->GetBufferPointer(), vertices.data(), vbByteSize);

	ThrowIfFailed(D3DCreateBlob(ibByteSize, &mBoxGeo->IndexBufferCPU));
	CopyMemory(mBoxGeo->IndexBufferCPU->GetBufferPointer(), indices.data(), ibByteSize);

	mBoxGeo->VertexBufferGPU = CreateDefaultbuffer(md3dDevice.Get(), mCommandList.Get(), vertices.data(), vbByteSize, mBoxGeo->VertexBufferUploader);
	mBoxGeo->IndexBufferGPU = CreateDefaultbuffer(md3dDevice.Get(), mCommandList.Get(), indices.data(), ibByteSize, mBoxGeo->IndexBufferUploader);

	mBoxGeo->VertexByteStride = sizeof(Vertex);
	mBoxGeo->VertexBufferByteSize = vbByteSize;
	mBoxGeo->IndexFormat = DXGI_FORMAT_R16_UINT;
	mBoxGeo->IndexBufferByteSize = ibByteSize;

	SubmeshGeometry submesh;
	submesh.IndexCount = static_cast<UINT>(indices.size());
	submesh.StartIndexLocation = 0;
	submesh.BaseVertexLocation = 0;

	mBoxGeo->DrawArgs["box"] = submesh;
}

Model의 Vertices와 Indices를 자성해주고 이를 각각의 Buffer에 쌓아준다.

 

마지막으로 볼 Build 함수는 BuildPSO이다.

void BoxApp::BuildPSO()
{
	D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc;
	ZeroMemory(&psoDesc, sizeof(D3D12_GRAPHICS_PIPELINE_STATE_DESC));
	psoDesc.InputLayout = D3D12_INPUT_LAYOUT_DESC{ mInputLayout.data(), static_cast<UINT>(mInputLayout.size()) };
	psoDesc.pRootSignature = mRootSignature.Get();
	psoDesc.VS =
	{
		reinterpret_cast<BYTE*>(mvsByteCode->GetBufferPointer()),
		mvsByteCode->GetBufferSize()
	};
	psoDesc.PS =
	{
		reinterpret_cast<BYTE*>(mpsByteCode->GetBufferPointer()),
		mpsByteCode->GetBufferSize()
	};
	
	D3D12_RASTERIZER_DESC Rasterizer;
	Rasterizer.FillMode = D3D12_FILL_MODE_SOLID;
	Rasterizer.CullMode = D3D12_CULL_MODE_BACK;
	Rasterizer.FrontCounterClockwise = FALSE;
	Rasterizer.DepthBias = D3D12_DEFAULT_DEPTH_BIAS;
	Rasterizer.DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP;
	Rasterizer.SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS;
	Rasterizer.DepthClipEnable = TRUE;
	Rasterizer.MultisampleEnable = FALSE;
	Rasterizer.AntialiasedLineEnable = FALSE;
	Rasterizer.ForcedSampleCount = 0;
	Rasterizer.ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF;
	
	D3D12_BLEND_DESC Blend;
	Blend.AlphaToCoverageEnable = FALSE;
	Blend.IndependentBlendEnable = FALSE;
	const D3D12_RENDER_TARGET_BLEND_DESC defaultRenderTargetBlendDesc
	{
		FALSE, FALSE, 
		D3D12_BLEND_ONE, D3D12_BLEND_ZERO, D3D12_BLEND_OP_ADD, 
		D3D12_BLEND_ONE, D3D12_BLEND_ZERO, D3D12_BLEND_OP_ADD, 
		D3D12_LOGIC_OP_NOOP, 
		D3D12_COLOR_WRITE_ENABLE_ALL,
	};
	for (auto& iter : Blend.RenderTarget)
	{
		iter = defaultRenderTargetBlendDesc;
	}

	D3D12_DEPTH_STENCIL_DESC DepthStencil;
	DepthStencil.DepthEnable = TRUE;
	DepthStencil.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL;
	DepthStencil.DepthFunc = D3D12_COMPARISON_FUNC_LESS;
	DepthStencil.StencilEnable = FALSE;
	DepthStencil.StencilReadMask = D3D12_DEFAULT_STENCIL_READ_MASK;
	DepthStencil.StencilWriteMask = D3D12_DEFAULT_STENCIL_WRITE_MASK;
	const D3D12_DEPTH_STENCILOP_DESC defaultStencilOp = 
	{
		D3D12_STENCIL_OP_KEEP, 
		D3D12_STENCIL_OP_KEEP,
		D3D12_STENCIL_OP_KEEP,
		D3D12_COMPARISON_FUNC_ALWAYS
	};
	DepthStencil.FrontFace = defaultStencilOp;
	DepthStencil.BackFace = defaultStencilOp;

	psoDesc.RasterizerState = Rasterizer;
	psoDesc.BlendState = Blend;
	psoDesc.DepthStencilState = DepthStencil;
	psoDesc.SampleMask = UINT_MAX;
	psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
	psoDesc.NumRenderTargets = 1;
	psoDesc.RTVFormats[0] = mBackBufferFormat;
	psoDesc.SampleDesc.Count = m4xMsaaState ? 4 : 1;
	psoDesc.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
	psoDesc.DSVFormat = mDepthStencilFormat;
	ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&mPSO)));
}

굉장히 길지만 내용은 별거 없다.

1. Pipeline State Object를 생성하고 정보를 채워넣어준다.

2. 안에서 사용할 Rasterizer, DepthStencil, Blend 객체들을 생성해서 채워넣어준다.

외부 지원 함수를 사용하지 않아서 이렇게 긴 것일 뿐이다.

 

기나긴 Build 함수들을 뒤로하고 다시 Override 되는 기능 함수들을 살펴보자.

OnResize 함수이다.

void BoxApp::OnResize()
{
	D3DApp::OnResize();

	XMMATRIX P = XMMatrixPerspectiveFovLH(XM_PIDIV4, AspectRatio(), 1.0f, 1000.0f);
	XMStoreFloat4x4(&mProj, P);
}

기본적인 화면 크기 변경에 따른 기능들은 상위 클래스에서 해결해준다.

여기서는 Fov(File of View)LH(Left-Handed)를 조정하여 Model의 종횡비를 조절해준다.

 

지금부터는 상위 클래스에서 비어 있었던 함수들이다.

가장 먼저 볼 것은 Update이다.

void BoxApp::Update(const GameTimer& gt)
{
	float x = mRadius * sinf(mPhi) * cosf(mTheta);
	float z = mRadius * sinf(mPhi) * sinf(mTheta);
	float y = mRadius * cosf(mPhi);

	XMVECTOR pos = XMVectorSet(x, y, z, 1.0f);
	XMVECTOR target = XMVectorZero();
	XMVECTOR up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);

	XMMATRIX view = XMMatrixLookAtLH(pos, target, up);
	XMStoreFloat4x4(&mView, view);

	XMMATRIX world = XMLoadFloat4x4(&mWorld);
	XMMATRIX proj = XMLoadFloat4x4(&mProj);
	XMMATRIX worldViewProj = world * view * proj;

	ObjectConstants objConstants;
	XMStoreFloat4x4(&objConstants.WorldViewProj, XMMatrixTranspose(worldViewProj));
	mObjectCB->CopyData(0, objConstants);
}

매 프레임마다 호출되는 Update에서는 현재까지의 입력값을 통해 Model의 각 Vertex들이 갖는 좌표 값을 연산해낸다.

좌표값은 mPhi나 mTheta, mRadius에 저장되어 있으며, 값이 변하는 시기는 OnMouseMove이다.

void BoxApp::OnMouseMove(WPARAM btnState, int x, int y)
{
	if ((btnState & MK_LBUTTON) != 0)
	{
		float dx = XMConvertToRadians(0.25f * static_cast<float>(x - mLastMousePos.x));
		float dy = XMConvertToRadians(0.25f * static_cast<float>(y - mLastMousePos.y));

		mTheta += dx;
		mPhi += dy;

		mPhi = Clamp(mPhi, 0.1f, XM_PI - 0.1f);
	}
	else if ((btnState && MK_RBUTTON) != 0)
	{
		float dx = 0.005f * static_cast<float>(x - mLastMousePos.x);
		float dy = 0.005f * static_cast<float>(y - mLastMousePos.y);

		mRadius += dx - dy;

		mRadius = Clamp(mRadius, 3.f, 15.f);
	}
	mLastMousePos.x = x;
	mLastMousePos.y = y;
}

좌클릭과 우클릭 시 좌표 값에 따라 회전이나 크기 값에 영향을 준다.

 

더불어 다른 Mouse Cursor 입력과 관련된 OnMouseDown, OnMouseUP 함수는 다음과 같다.

void BoxApp::OnMouseDown(WPARAM btnState, int x, int y)
{
	mLastMousePos.x = x;
	mLastMousePos.y = y;
	SetCapture(mhMainWnd);
}

void BoxApp::OnMouseUp(WPARAM btnState, int x, int y)
{
	ReleaseCapture();
}

 

 

마지막 남은 Draw 함수를 살펴보자. 이 함수는 조금 어렵다.

void BoxApp::Draw(const GameTimer& gt)
{
	ThrowIfFailed(mDirectCmdListAlloc->Reset());
	ThrowIfFailed(mCommandList->Reset(mDirectCmdListAlloc.Get(), mPSO.Get()));

	// Set CommandList
	mCommandList->RSSetViewports(1, &mScreenViewport);
	mCommandList->RSSetScissorRects(1, &mScissorRect);

	// Set CommandList - not problem
	D3D12_RESOURCE_BARRIER BeforeBarrier;
	ZeroMemory(&BeforeBarrier, sizeof(BeforeBarrier));
	BeforeBarrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
	BeforeBarrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
	BeforeBarrier.Transition.pResource = CurrentBackBuffer();
	BeforeBarrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
	BeforeBarrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
	BeforeBarrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
	

	mCommandList->ResourceBarrier(1, &BeforeBarrier);

	mCommandList->ClearRenderTargetView(CurrentBackBufferView(), Colors::LightSteelBlue, 0, nullptr);
	mCommandList->ClearDepthStencilView(DepthStencilView(), D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr);

	// Specify the buffers we are going to render to.
	// Set CommandList
	mCommandList->OMSetRenderTargets(1, &CurrentBackBufferView(), true, &DepthStencilView());

	ID3D12DescriptorHeap* descriptorHeaps[] = { mCbvHeap.Get() };
	mCommandList->SetDescriptorHeaps(_countof(descriptorHeaps), descriptorHeaps);

	// Set CommandList
	mCommandList->SetGraphicsRootSignature(mRootSignature.Get());

	// Set CommandList
	mCommandList->IASetVertexBuffers(0, 1, &mBoxGeo->VertexBufferView());
	mCommandList->IASetIndexBuffer(&mBoxGeo->IndexBufferView());
	mCommandList->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

	// Set CommandList
	mCommandList->SetGraphicsRootDescriptorTable(0, mCbvHeap->GetGPUDescriptorHandleForHeapStart());

	mCommandList->DrawIndexedInstanced(mBoxGeo->DrawArgs["box"].IndexCount, 1, 0, 0, 0);

	// Set CommandList - not problem
	D3D12_RESOURCE_BARRIER AfterBarrier;
	ZeroMemory(&AfterBarrier, sizeof(AfterBarrier));
	AfterBarrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
	AfterBarrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
	AfterBarrier.Transition.pResource = CurrentBackBuffer();
	AfterBarrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
	AfterBarrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
	AfterBarrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;

	mCommandList->ResourceBarrier(1, &AfterBarrier);
	ThrowIfFailed(mCommandList->Close());

	ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };
	mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);

	ThrowIfFailed(mSwapChain->Present(0, 0));
	mCurrentBackBuffer = (mCurrentBackBuffer + 1) % SwapChainBufferCount;

	FlushCommandQueue();
}

그리기 작업도 결국 명령이기에 CommandList를 거친다.

그렇기에 CommandListAllocator와 CommandList를 초기화해주고, 필요한 설정을 해준다.

그리고 명령을 입력하기 전 후로 Barrier를 생성해서 추가해 명령의 구분선을 그어준다.

 

실질적인 그리기 명령은 Render Target View와 Depth Stencil View를 초기화 하고, RenderTarget을 지정해준다.

다음 Descriptor Heaps에 Constant Buffer View를 가져다가 설정해주고, RootSignature를 설정한다.

이후 VertexBuffer, IndexBuffer를 채워넣어주고 Privitive Topology 생성, RootDescriptorTable 설정을 한 뒤

그리기 함수를 호출한다.

 

마무리 Barrier가 채워지면 CommandQueue를 실행하고 이를 비워준다.

여기서 우리가 기억해야 할 것은 3가지이다.

1. 명령 전에는 항상 Command List를 초기화 해주고,

명령 후에는 이를 복사한 뒤 CommandQueue에 넣고 실행.

실행 후에는 CommandQueue도 Flush.

2. 명령 전후에는 항상 Barrier를 생성.

3. DirectX12의 렌더링 방식에 따른 그리기 함수 호출.

 

이로써 DirectX와 Window를 생성해주는 모든 코드를 살펴보았다.

마무리로 Main함수를 살펴보자.

// DirectX12ExampleCode.cpp : Defines the entry point for the application.
//

#include "framework.h"
#include "DirectX12ExampleCode.h"
#include "Example/BoxApp.h"

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // TODO: Place code here.
    D3DApp* App = new BoxApp(hInstance);

    // Perform application initialization:
    if (!App->Initialize())
    {
        return FALSE;
    }

    // Main message loop:
    try
    {
        return App->run();
    }
    catch (DXException& e)
    {
        MessageBox(nullptr, e.ToString().c_str(), L"HR Failed", MB_OK);
        return 0;
    }
}

 

주석으로 처리된 원래 함수들은 삭제했다.

전체적인 선언이나 구성은 무시해도 좋다. 어차피 Windows 관련 배경지식이 있어야 한다.

그저 Try-Catch로 run 함수에서의 에러를 잡아준다는 것만 보고 가자.

 

이로써 기나긴 BoxApp 예시를 모두 살펴보았습니다.

다음에는 외부 코드를 적절히 프로젝트에 적용해서 코드를 간략화 할 것입니다.

이 과정이 끝나면 책에서의 Chapter6를 보고 정리한 뒤, 다음 Chapter로 넘어갈 계획입니다.

'내용정리 > DirectX12' 카테고리의 다른 글

20.09.18 개발일지  (0) 2020.09.18
10. Box Example 2 - D3DApp  (0) 2020.09.08
10. Box Example 1  (0) 2020.09.04
20.08.25 개발일지  (0) 2020.08.25
20.08.21 개발일지  (0) 2020.08.21

이제 우리가 봐야 할 남은 코드는 두 가지입니다.

하나는 DirectX12 예시 프로젝트의 기반이 되는 Base Code인 D3DApp.

그리고 다른 하나는 이번 Box Example의 코드인 BoxApp.

 

이 글에서는 D3DApp.h를 보겠습니다. 전체 코드는 꽤 기니까 접은글로 올리겠습니다.

더보기
#pragma once
#include "Common.h"

class D3DApp
{
protected:
	D3DApp(HINSTANCE hInstance);
	D3DApp(const D3DApp& rhs) = delete;
	D3DApp& operator=(const D3DApp& rhs) = delete;
	virtual ~D3DApp();

public:
	static D3DApp* GetApp();
	HINSTANCE	AppInst() const;
	HWND		MainWnd() const;
	float		AspectRatio() const;

	bool Get4xMsaaState() const;
	void Set4xMsaaState(bool value);

	int run();

	virtual bool Initialize();
	virtual LRESULT MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

protected:
	virtual void CreateRtvAndDsvDescriptorHeaps();
	virtual void OnResize();
	virtual void Update(const GameTimer& gt) = 0;
	virtual void Draw(const GameTimer& gt) = 0;

	virtual void OnMouseDown(WPARAM btnState, int x, int y) = 0;
	virtual void OnMouseUp(WPARAM btnState, int x, int y) = 0;
	virtual void OnMouseMove(WPARAM btnState, int x, int y) = 0;

protected:
	bool InitMainWindow();
	bool InitDirect3D();
	void CreateCommandObjects();
	void CreateSwapChain();

	void FlushCommandQueue();

	ID3D12Resource* CurrentBackBuffer() const;
	D3D12_CPU_DESCRIPTOR_HANDLE CurrentBackBufferView() const;
	D3D12_CPU_DESCRIPTOR_HANDLE DepthStencilView() const;

	void CalculateFrameStats();

	void LogAdpaters();
	void LogAdpaterOutputs(IDXGIAdapter* adapter);
	void LogOutputDisplayModes(IDXGIOutput* output, DXGI_FORMAT format);

private:
	ATOM MyRegisterClass(WNDCLASSEXW& wc);
	BOOL InitInstance(int nCmdShow);
	INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
protected:
	static D3DApp* mApp;

	HINSTANCE	mhAppInst = nullptr;
	HWND		mhMainWnd = nullptr;
	bool		mAppPaused = false;
	bool		mMinimized = false;
	bool		mMaximized = false;
	bool		mResizing = false;
	bool		mFullscreenState = false;

	bool		m4xMsaaState = false;
	UINT		m4xMsaaQuality = 0;

	GameTimer mTimer;

	Microsoft::WRL::ComPtr<IDXGIFactory4> mdxgiFactory;
	Microsoft::WRL::ComPtr<IDXGISwapChain> mSwapChain;
	Microsoft::WRL::ComPtr<ID3D12Device> md3dDevice;

	Microsoft::WRL::ComPtr<ID3D12Fence> mFence;
	UINT64 mCurrentFence = 0;

	Microsoft::WRL::ComPtr<ID3D12CommandQueue> mCommandQueue;
	Microsoft::WRL::ComPtr<ID3D12CommandAllocator> mDirectCmdListAlloc;
	Microsoft::WRL::ComPtr<ID3D12GraphicsCommandList> mCommandList;

	static const int SwapChainBufferCount = 2;
	int mCurrentBackBuffer = 0;
	Microsoft::WRL::ComPtr<ID3D12Resource> mSwapChainbuffer[SwapChainBufferCount];
	Microsoft::WRL::ComPtr<ID3D12Resource> mDepthStencilBuffer;

	Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> mRtvHeap;
	Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> mDsvHeap;

	D3D12_VIEWPORT mScreenViewport;
	D3D12_RECT mScissorRect;

	UINT mRtvDescriptorSize = 0;
	UINT mDsvDescriptorSize = 0;
	UINT mCbvSrvUavDescriptorSize = 0;

	std::wstring mMainWndCaption = L"d3d App";
	D3D_DRIVER_TYPE md3dDriverType = D3D_DRIVER_TYPE_HARDWARE;
	DXGI_FORMAT mBackBufferFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
	DXGI_FORMAT mDepthStencilFormat = DXGI_FORMAT_D24_UNORM_S8_UINT;
	int mClientWidth = 960;
	int mClientHeight = 540;
};

우선 생성자와 소멸자 부분을 보면 복사생성자, 대입연산자가 막혀있고 Protected로 선언되어 있습니다.

protected:
	D3DApp(HINSTANCE hInstance);
	D3DApp(const D3DApp& rhs) = delete;
	D3DApp& operator=(const D3DApp& rhs) = delete;
	virtual ~D3DApp();

이는 원본 코드에서 Main 함수를 포함하고 있었기에 Singleton으로 구현이 되어있기 때문입니다.

이에 대한 흔적은 static으로 함수와 변수들로부터 그 흔적을 발견할수도 있습니다.

물론 원래는 모두 제거해야 할 흔적들이나, 미처 하지 못했고, 나중에 시간이 남으면 한번 해보겠습니다.

 

그 다음 Public 부분.

public:
	static D3DApp* GetApp();
	HINSTANCE	AppInst() const;
	HWND		MainWnd() const;
	float		AspectRatio() const;

	bool Get4xMsaaState() const;
	void Set4xMsaaState(bool value);

	int run();

	virtual bool Initialize();
	virtual LRESULT MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

DirectX에서의 초기화 부분과 Window 생성 부분의 함수들이 혼용되고 있습니다.

역시 나중에 절반이 사라질 예정입니다.

 

이 중 run 함수는 실질적인 Rendering을 시작하는 함수이고, 

Initialize는 초기화 함수입니다.

보통은 생성자에서는 변수만 초기화 하고, 초기화 함수를 두어 DirectX 설정을 따로 초기화 합니다.

예시에서도 그렇고, 상용 게임 엔진에서도 BeginPlay라는 함수가 따로 존재합니다.

보통 "Compile 상에서 초기화 하느냐"와 "Runtime 상에서 초기화 하느냐"로 나뉘어 집니다.

 

그 아래 함수들을 보겠습니다.

protected:
	virtual void CreateRtvAndDsvDescriptorHeaps();
	virtual void OnResize();
	virtual void Update(const GameTimer& gt) = 0;
	virtual void Draw(const GameTimer& gt) = 0;

	virtual void OnMouseDown(WPARAM btnState, int x, int y) = 0;
	virtual void OnMouseUp(WPARAM btnState, int x, int y) = 0;
	virtual void OnMouseMove(WPARAM btnState, int x, int y) = 0;

 

함수들이 혼용되어 있는데 On으로 시작하는 함수는 모두 Window Event 관련 함수라 보시면 됩니다.

아마 Mouse 입력 관련된 함수들은 그대로 사용되겠지만,

OnResize는 밖으로 떨어져 나가면 꽤 많은 입력값을 요구할 것입니다.

 

Update는 매 Tick마다 발생하는 변화를 적용하고, Draw는 그때마다 프레임들을 새로 그려줍니다.

CreateRtvAndDsvDescriptorHeaps는 Renter Target View와 Depth Stencil View의 Descriptor Heaps를 생성해준다.

Descriptor는 Resource를 GPU에 서술하는 자료구조이다.

Descriptor Heap은 이런 Descriptor의 배열로, CPU에 의해서만 즉시 편집될 수 있고 GPU에서는 편집을 할 수 없다.

 

이에 대해 아직 설명을 한 적이 없는데 중간에 책을 바꿔서 누락이 된건지, 코드 설명과 같이 나오는 이론인지 모르겠다.

그러니 우선 참고자료를 첨부하고 넘어가겠다.

docs.microsoft.com/ko-kr/windows/win32/direct3d12/resource-binding-flow-of-control

 

리소스 바인딩 개요 - Win32 apps

DirectX 12에서 리소스 바인딩의 핵심은 설명자, 설명자 테이블, 설명자 힙 및 루트 서명 개념입니다.

docs.microsoft.com

docs.microsoft.com/ko-kr/windows/win32/direct3d12/descriptor-heaps-overview?redirectedfrom=MSDN

 

설명자 힙 개요 - Win32 apps

설명자 힙은 PSO(파이프라인 상태 개체)에 속하지 않는 많은 개체 유형[예: SRV(셰이더 리소스 뷰), UAV(순서가 지정되지 않은 액세스 뷰), CBV(상수 버퍼 뷰) 및 샘플러]을 포함합니다.

docs.microsoft.com

hannom.tistory.com/158

 

[DirectX12]기본지식 - 자원과 서술자(resource와 descriptor)

렌더링 과정에서 GPU는 자원들에 자료를 기록하거나 자원(resource)들에서 자료를 읽어들이고 그리기 명령을 제출하기 전에 먼저 해당 그리기 호출이 참조한 자원들을 렌더링 파이프라인에 묶어야

hannom.tistory.com

 

다음 선언부를 살펴봅시다.

protected:
	bool InitMainWindow();
	bool InitDirect3D();
	void CreateCommandObjects();
	void CreateSwapChain();

	void FlushCommandQueue();

	ID3D12Resource* CurrentBackBuffer() const;
	D3D12_CPU_DESCRIPTOR_HANDLE CurrentBackBufferView() const;
	D3D12_CPU_DESCRIPTOR_HANDLE DepthStencilView() const;

	void CalculateFrameStats();

	void LogAdpaters();
	void LogAdpaterOutputs(IDXGIAdapter* adapter);
	void LogOutputDisplayModes(IDXGIOutput* output, DXGI_FORMAT format);

InitMainWindow는 당연히 Window 초기화 함수입니다.

코드는 작성할 것이지만 자세한 설명은 생략하겠습니다.

InitDirect3D는 DirectX와 관련된 초기화를 담당합니다.

그 밑에 함수들은 이름 그대로의 긴으을 가지고 있습니다.

 

CalculateFrameStats부터 아래의 함수들은 기능과는 상관 없는 함수들입니다.

예시 코드에서 기본 성능을 화면에 출력하는데 이와 관련된 함수들입니다.

별도로 수정 될 이유도 없고, 있는 그대로 적어 두시면 될 것입니다.

 

마지막 선언부입니다.

private:
	ATOM MyRegisterClass(WNDCLASSEXW& wc);
	BOOL InitInstance(int nCmdShow);
	INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);

Window 관련 함수들입니다. 가볍게 무시하고 넘어가겠습니다.

 

남은 것은 변수들입니다.

protected:
	static D3DApp* mApp;

	HINSTANCE	mhAppInst = nullptr;
	HWND		mhMainWnd = nullptr;
	bool		mAppPaused = false;
	bool		mMinimized = false;
	bool		mMaximized = false;
	bool		mResizing = false;
	bool		mFullscreenState = false;

	bool		m4xMsaaState = false;
	UINT		m4xMsaaQuality = 0;

	GameTimer mTimer;

	Microsoft::WRL::ComPtr<IDXGIFactory4> mdxgiFactory;
	Microsoft::WRL::ComPtr<IDXGISwapChain> mSwapChain;
	Microsoft::WRL::ComPtr<ID3D12Device> md3dDevice;

	Microsoft::WRL::ComPtr<ID3D12Fence> mFence;
	UINT64 mCurrentFence = 0;

	Microsoft::WRL::ComPtr<ID3D12CommandQueue> mCommandQueue;
	Microsoft::WRL::ComPtr<ID3D12CommandAllocator> mDirectCmdListAlloc;
	Microsoft::WRL::ComPtr<ID3D12GraphicsCommandList> mCommandList;

	static const int SwapChainBufferCount = 2;
	int mCurrentBackBuffer = 0;
	Microsoft::WRL::ComPtr<ID3D12Resource> mSwapChainbuffer[SwapChainBufferCount];
	Microsoft::WRL::ComPtr<ID3D12Resource> mDepthStencilBuffer;

	Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> mRtvHeap;
	Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> mDsvHeap;

	D3D12_VIEWPORT mScreenViewport;
	D3D12_RECT mScissorRect;

	UINT mRtvDescriptorSize = 0;
	UINT mDsvDescriptorSize = 0;
	UINT mCbvSrvUavDescriptorSize = 0;

	std::wstring mMainWndCaption = L"d3d App";
	D3D_DRIVER_TYPE md3dDriverType = D3D_DRIVER_TYPE_HARDWARE;
	DXGI_FORMAT mBackBufferFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
	DXGI_FORMAT mDepthStencilFormat = DXGI_FORMAT_D24_UNORM_S8_UINT;
	int mClientWidth = 960;
	int mClientHeight = 540;

GameTimer 위의 선언부와 가장 아래 선언부 대부분은 Window 생성과 관련된 변수들입니다.

이들을 무시하고, 대부분은 이름을 보면 어떤 것인지 알 수 있을 것입니다.

 

하나하나 설명을 해도 좋겠지만 그러지 않는 이유는

1. 제가 아직 그 정도로 머리에 다 채워넣지 못했습니다.

아직은 보고 이해를 하는 수준이라서 글이 읽기 편하게 작성이 되지는 않습니다.

2. 보면 아는 내용을 굳이 적어서 시간을 낭비하고 글을 길게 늘리고 싶지 않습니다.

 

그러니 구현부로 넘어가보겠습니다.

더보기
#include "D3DApp.h"
#include <cassert>

using namespace std;
using namespace Microsoft::WRL;

LRESULT CALLBACK
MainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	return D3DApp::GetApp()->MsgProc(hwnd, msg, wParam, lParam);
}

D3DApp* D3DApp::mApp = nullptr;
D3DApp* D3DApp::GetApp()
{
	return mApp;
}

D3DApp::D3DApp(HINSTANCE hInstance) : mhAppInst(hInstance)
{
	assert(mApp == nullptr);
	mApp = this;
}

D3DApp::~D3DApp()
{
	if (md3dDevice != nullptr)
	{
		FlushCommandQueue();
	}
}

HINSTANCE D3DApp::AppInst() const
{
	return mhAppInst;
}

HWND D3DApp::MainWnd() const
{
	return mhMainWnd;
}

float D3DApp::AspectRatio() const
{
	return static_cast<float>(mClientWidth) / mClientHeight;
}

bool D3DApp::Get4xMsaaState() const
{
	return m4xMsaaState;
}

void D3DApp::Set4xMsaaState(bool value)
{
	if (m4xMsaaState != value)
	{
		m4xMsaaState = value;
		CreateSwapChain();
		OnResize();
	}
}

int D3DApp::run()
{
	//HACCEL hAccelTable = LoadAccelerators(mhAppInst, MAKEINTRESOURCE(IDC_DIRECTX12EXAMPLECODE));
	MSG msg{ 0 };
	mTimer.Reset();

	while (msg.message != WM_QUIT)
	{
		//TranslateAccelerator(msg.hwnd, hAccelTable, &msg)
		if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else
		{
			mTimer.Tick();

			if (!mAppPaused)
			{
				CalculateFrameStats();
				Update(mTimer);
				Draw(mTimer);
			}
			else
			{
				Sleep(100);
			}
		}
	}
	return static_cast<int>(msg.wParam);
}

bool D3DApp::Initialize()
{
	if (!InitMainWindow())
	{
		return false;
	}
	if (!InitDirect3D())
	{
		return false;
	}

	OnResize();
	return true;
}

void D3DApp::CreateRtvAndDsvDescriptorHeaps()
{
	D3D12_DESCRIPTOR_HEAP_DESC rtvheapDesc;
	rtvheapDesc.NumDescriptors = SwapChainBufferCount;
	rtvheapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
	rtvheapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
	rtvheapDesc.NodeMask = 0;
	ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&rtvheapDesc, IID_PPV_ARGS(mRtvHeap.GetAddressOf())));

	D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc;
	dsvHeapDesc.NumDescriptors = 1;
	dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
	dsvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
	dsvHeapDesc.NodeMask = 0;
	ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&dsvHeapDesc, IID_PPV_ARGS(mDsvHeap.GetAddressOf())));
}

void D3DApp::OnResize()
{
	assert(md3dDevice);
	assert(mSwapChain);
	assert(mDirectCmdListAlloc);

	FlushCommandQueue();

	ThrowIfFailed(mCommandList->Reset(mDirectCmdListAlloc.Get(), nullptr));
	
	for (int i = 0; i < SwapChainBufferCount; ++i)
	{
		mSwapChainbuffer[i].Reset();
	}
	mDepthStencilBuffer.Reset();

	ThrowIfFailed(mSwapChain->ResizeBuffers(
		SwapChainBufferCount, 
		mClientWidth, 
		mClientHeight, 
		mBackBufferFormat, 
		DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH));

	mCurrentBackBuffer = 0;

	D3D12_CPU_DESCRIPTOR_HANDLE rtvHeapHandle(mRtvHeap->GetCPUDescriptorHandleForHeapStart());
	for (UINT i = 0; i < SwapChainBufferCount; ++i)
	{
		ThrowIfFailed(mSwapChain->GetBuffer(i, IID_PPV_ARGS(&mSwapChainbuffer[i])));
		md3dDevice->CreateRenderTargetView(mSwapChainbuffer[i].Get(), nullptr, rtvHeapHandle);
		rtvHeapHandle.ptr += mRtvDescriptorSize;
	}

	D3D12_RESOURCE_DESC depthStencilDesc;
	depthStencilDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
	depthStencilDesc.Alignment = 0;
	depthStencilDesc.Width = mClientWidth;
	depthStencilDesc.Height = mClientHeight;
	depthStencilDesc.DepthOrArraySize = 1;
	depthStencilDesc.MipLevels = 1;
	
	depthStencilDesc.Format = DXGI_FORMAT_R24G8_TYPELESS;
	depthStencilDesc.SampleDesc.Count = m4xMsaaState ? 4 : 1;
	depthStencilDesc.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
	depthStencilDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
	depthStencilDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;

	D3D12_CLEAR_VALUE optClear;
	optClear.Format = mDepthStencilFormat;
	optClear.DepthStencil.Depth = 1.f;
	optClear.DepthStencil.Stencil = 0;
	
	D3D12_HEAP_PROPERTIES HeapProperty;
	HeapProperty.Type = D3D12_HEAP_TYPE_DEFAULT;
	HeapProperty.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
	HeapProperty.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
	HeapProperty.CreationNodeMask = 1;
	HeapProperty.VisibleNodeMask = 1;

	ThrowIfFailed(md3dDevice->CreateCommittedResource(
		&HeapProperty, D3D12_HEAP_FLAG_NONE, 
		&depthStencilDesc, D3D12_RESOURCE_STATE_COMMON, 
		&optClear, IID_PPV_ARGS(mDepthStencilBuffer.GetAddressOf())));

	D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc;
	dsvDesc.Flags = D3D12_DSV_FLAG_NONE;
	dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
	dsvDesc.Format = mDepthStencilFormat;
	dsvDesc.Texture2D.MipSlice = 0;
	md3dDevice->CreateDepthStencilView(mDepthStencilBuffer.Get(), &dsvDesc, DepthStencilView());

	D3D12_RESOURCE_BARRIER Barrier;
	ZeroMemory(&Barrier, sizeof(Barrier));
	Barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
	Barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
	Barrier.Transition.pResource = mDepthStencilBuffer.Get();
	Barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COMMON;
	Barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_DEPTH_WRITE;
	mCommandList->ResourceBarrier(1, &Barrier);

	ThrowIfFailed(mCommandList->Close());
	ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };
	mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);

	FlushCommandQueue();

	mScreenViewport.TopLeftX = 0;
	mScreenViewport.TopLeftY = 0;
	mScreenViewport.Width = static_cast<float>(mClientWidth);
	mScreenViewport.Height = static_cast<float>(mClientHeight);
	mScreenViewport.MinDepth = 0.0f;
	mScreenViewport.MaxDepth = 1.0f;

	mScissorRect = { 0, 0, mClientWidth, mClientHeight };
}

LRESULT D3DApp::MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	//WndProc
	switch (msg)
	{
	case WM_COMMAND:
	{
		int wmId = LOWORD(wParam);
		// Parse the menu selections:
		switch (wmId)
		{
		case IDM_ABOUT:
			//DialogBox(mhAppInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, About);
			break;
		case ID_EXAMPLE_CHAPTER6:

			break;
		case IDM_EXIT:
			DestroyWindow(hwnd);
			break;
		default:
			return DefWindowProc(hwnd, msg, wParam, lParam);
		}
	}
		break;
	case WM_PAINT:
	{
		PAINTSTRUCT ps;
		HDC hdc = BeginPaint(hwnd, &ps);
		// TODO: Add any drawing code that uses hdc here...
		EndPaint(hwnd, &ps);
	}
	break;
	case WM_ACTIVATE:
		if (LOWORD(wParam) == WA_INACTIVE)
		{
			mAppPaused = true;
			mTimer.Stop();
		}
		else
		{
			mAppPaused = false;
			mTimer.Start();
		}
		return 0;
	case WM_SIZE:
		mClientWidth = LOWORD(lParam);
		mClientHeight = HIWORD(lParam);
		if (md3dDevice)
		{
			if (wParam == SIZE_MINIMIZED)
			{
				mAppPaused = true;
				mMinimized = true;
				mMaximized = false;
			}
			else if (wParam == SIZE_MAXIMIZED)
			{
				mAppPaused = false;
				mMinimized = false;
				mMaximized = true;
				OnResize();
			}
			else if (wParam == SIZE_RESTORED)
			{
				if (mMinimized)
				{
					mAppPaused = false;
					mMinimized = false;
					OnResize();
				}
				else if (mMaximized)
				{
					mAppPaused = false;
					mMaximized = false;
					OnResize();
				}
				else if (mResizing)
				{

				}
				else
				{
					OnResize();
				}
			}
		}
		return 0;
	case WM_ENTERSIZEMOVE:
		mAppPaused = true;
		mResizing = true;
		mTimer.Stop();
		return 0;
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_MENUCHAR:
		return MAKELRESULT(0, MNC_CLOSE);
	case WM_GETMINMAXINFO:
		((MINMAXINFO*)lParam)->ptMinTrackSize.x = 200;
		((MINMAXINFO*)lParam)->ptMinTrackSize.y = 200;
		return 0;
	case WM_LBUTTONDOWN:
	case WM_MBUTTONDOWN:
	case WM_RBUTTONDOWN:
		OnMouseDown(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
		return 0;
	case WM_LBUTTONUP:
	case WM_MBUTTONUP:
	case WM_RBUTTONUP:
		OnMouseUp(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
		return 0;
	case WM_MOUSEMOVE:
		OnMouseMove(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
		return 0;
	case WM_KEYUP:
		if (wParam == VK_ESCAPE)
		{
			PostQuitMessage(0);
		}
		else if (static_cast<int>(wParam) == VK_F2)
		{
			Set4xMsaaState(!m4xMsaaState);
		}
		return 0;
	}
	return DefWindowProc(hwnd, msg, wParam, lParam);
}

bool D3DApp::InitMainWindow()
{
	WNDCLASSEXW wc;
	if (!MyRegisterClass(wc))
	{
		MessageBox(0, L"RegisterClass Failed.", 0, 0);
		return false;
	}
	return InitInstance(SW_SHOW);
}

bool D3DApp::InitDirect3D()
{
#if defined(DEBUG) || defined(_DEBUG)
	{
		ComPtr<ID3D12Debug> debugController;
		ThrowIfFailed(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)));
		debugController->EnableDebugLayer();
	}
#endif

	ThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(&mdxgiFactory)));

	HRESULT hardwareResult = D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_12_1, IID_PPV_ARGS(&md3dDevice));

	if (FAILED(hardwareResult))
	{
		ComPtr<IDXGIAdapter4> pWarpAdapter;
		ThrowIfFailed(mdxgiFactory->EnumWarpAdapter(IID_PPV_ARGS(&pWarpAdapter)));

		ThrowIfFailed(D3D12CreateDevice(pWarpAdapter.Get(), D3D_FEATURE_LEVEL_12_1, IID_PPV_ARGS(&md3dDevice)));
	}

	ThrowIfFailed(md3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence)));

	mRtvDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
	mDsvDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
	mCbvSrvUavDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

	D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS msQualityLevels;
	msQualityLevels.Format = mBackBufferFormat;
	msQualityLevels.SampleCount = 4;
	msQualityLevels.Flags = D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE;
	msQualityLevels.NumQualityLevels = 0;
	ThrowIfFailed(md3dDevice->CheckFeatureSupport(D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS, &msQualityLevels, sizeof(msQualityLevels)));

	m4xMsaaQuality = msQualityLevels.NumQualityLevels;
	assert(m4xMsaaQuality > 0 && "Unexpected Msaa quality level.");

#ifdef _DEBUG
	LogAdpaters();
#endif
	
	CreateCommandObjects();
	CreateSwapChain();
	CreateRtvAndDsvDescriptorHeaps();

	return true;
}

void D3DApp::CreateCommandObjects()
{
	D3D12_COMMAND_QUEUE_DESC queueDesc = {};
	queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
	queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
	ThrowIfFailed(md3dDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&mCommandQueue)));

	ThrowIfFailed(md3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(mDirectCmdListAlloc.GetAddressOf())));

	ThrowIfFailed(md3dDevice->CreateCommandList(
		0, 
		D3D12_COMMAND_LIST_TYPE_DIRECT, 
		mDirectCmdListAlloc.Get(), 
		nullptr, 
		IID_PPV_ARGS(mCommandList.GetAddressOf())));

	mCommandList->Close();
}

void D3DApp::CreateSwapChain()
{
	mSwapChain.Reset();
	
	DXGI_SWAP_CHAIN_DESC sd;
	sd.BufferDesc.Width = mClientWidth;
	sd.BufferDesc.Height = mClientHeight;
	sd.BufferDesc.RefreshRate.Numerator = 60;
	sd.BufferDesc.RefreshRate.Denominator = 1;
	sd.BufferDesc.Format = mBackBufferFormat;
	sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
	sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
	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;
	sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
	

	ThrowIfFailed(mdxgiFactory->CreateSwapChain(mCommandQueue.Get(), &sd, mSwapChain.GetAddressOf()));
	//ThrowIfFailed(mdxgiFactory->CreateSwapChainForHwnd(mCommandQueue.Get(), mhMainWnd, &sd1, &swapChainFSDesc, nullptr, &mSwapChain));
	//not recommand to use CreateSwapChain
}

void D3DApp::FlushCommandQueue()
{
	++mCurrentFence;
	
	ThrowIfFailed(mCommandQueue->Signal(mFence.Get(), mCurrentFence));

	if (mFence->GetCompletedValue() < mCurrentFence)
	{
		HANDLE eventHandle = CreateEvent(nullptr, false, false, nullptr);
		//HANDLE eventHandle = CreateEventEx(nullptr, false, false, EVENT_ALL_ACCESS);
		//https://github.com/microsoft/DirectX-Graphics-Samples/issues/15

		ThrowIfFailed(mFence->SetEventOnCompletion(mCurrentFence, eventHandle));

		WaitForSingleObject(eventHandle, INFINITE);
		CloseHandle(eventHandle);
	}
}

ID3D12Resource* D3DApp::CurrentBackBuffer() const
{
	return mSwapChainbuffer[mCurrentBackBuffer].Get();
}

D3D12_CPU_DESCRIPTOR_HANDLE D3DApp::CurrentBackBufferView() const
{
	D3D12_CPU_DESCRIPTOR_HANDLE result(mRtvHeap->GetCPUDescriptorHandleForHeapStart());
	result.ptr += mCurrentBackBuffer * mRtvDescriptorSize;
	return result;
}

D3D12_CPU_DESCRIPTOR_HANDLE D3DApp::DepthStencilView() const
{
	return mDsvHeap->GetCPUDescriptorHandleForHeapStart();
}

void D3DApp::CalculateFrameStats()
{
	static int frameCnt = 0;
	static float timeElapse = 0.f;

	++frameCnt;

	if ((mTimer.TotalTime() - timeElapse) >= 1.f)
	{
		float fps = static_cast<float>(frameCnt);
		float mspf = 1000.f / fps;
		
		wstring fpsStr = to_wstring(fps);
		wstring mspStr = to_wstring(mspf);

		wstring windowText = mMainWndCaption + L",    fps: " + fpsStr + L"ms,    pf: " + mspStr;

		SetWindowText(mhMainWnd, windowText.c_str());
		frameCnt = 0;
		timeElapse += 1.f;
	}
}

void D3DApp::LogAdpaters()
{
	UINT i = 0;
	IDXGIAdapter* adapter = nullptr;
	std::vector<IDXGIAdapter*> adapterList;
	while (mdxgiFactory->EnumAdapters(i, &adapter) != DXGI_ERROR_NOT_FOUND)
	{
		DXGI_ADAPTER_DESC desc;
		adapter->GetDesc(&desc);

		std::wstring text = L"***Adpater: ";
		text += desc.Description;
		text += L"\n";

		OutputDebugString(text.c_str());

		adapterList.push_back(adapter);

		++i;
	}

	for (size_t i = 0; i < adapterList.size(); ++i)
	{
		LogAdpaterOutputs(adapterList[i]);
		if (adapterList[i])
		{
			adapterList[i]->Release();
			adapterList[i] = 0;
		}
	}
}

void D3DApp::LogAdpaterOutputs(IDXGIAdapter* adapter)
{
	UINT i = 0;
	IDXGIOutput* output = nullptr;
	while (adapter->EnumOutputs(i, &output) != DXGI_ERROR_NOT_FOUND)
	{
		DXGI_OUTPUT_DESC desc;
		output->GetDesc(&desc);

		std::wstring text = L"***Output: ";
		text += desc.DeviceName;
		text += L"\n";
		OutputDebugString(text.c_str());

		LogOutputDisplayModes(output, mBackBufferFormat);

		if (output)
		{
			output->Release();
			output = 0;
		}

		++i;
	}
}

void D3DApp::LogOutputDisplayModes(IDXGIOutput* output, DXGI_FORMAT format)
{
	UINT count = 0;
	UINT flags = 0;

	output->GetDisplayModeList(format, flags, &count, nullptr);

	std::vector<DXGI_MODE_DESC> modeList(count);
	output->GetDisplayModeList(format, flags, &count, &modeList[0]);

	for (const auto& x : modeList)
	{
		UINT n = x.RefreshRate.Numerator;
		UINT d = x.RefreshRate.Denominator;
		std::wstring text =
			L"Width = " + to_wstring(x.Width) + L" " +
			L"Height = " + to_wstring(x.Height) + L" " +
			L"Refresh = " + to_wstring(n) + L"/" + to_wstring(d) + L"\n";

		::OutputDebugString(text.c_str());
	}
}

//
//  FUNCTION: MyRegisterClass()
//
//  PURPOSE: Registers the window class.
//
ATOM D3DApp::MyRegisterClass(WNDCLASSEXW& wc)
{
	wc.cbSize = sizeof(WNDCLASSEX);
	wc.style = CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc = MainWndProc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = mhAppInst;
	wc.hIcon = LoadIcon(mhAppInst, MAKEINTRESOURCE(IDI_DIRECTX12EXAMPLECODE));
	wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
	wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wc.lpszMenuName = MAKEINTRESOURCEW(IDC_DIRECTX12EXAMPLECODE);
	wc.lpszClassName = L"MainWnd";
	wc.hIconSm = LoadIcon(wc.hInstance, MAKEINTRESOURCE(IDI_SMALL));
	return RegisterClassExW(&wc);
}

//
//   FUNCTION: InitInstance(HINSTANCE, int)
//
//   PURPOSE: Saves instance handle and creates main window
//
//   COMMENTS:
//
//        In this function, we save the instance handle in a global variable and
//        create and display the main program window.
//
BOOL D3DApp::InitInstance(int nCmdShow)
{
	RECT R = { 0, 0, mClientWidth, mClientHeight };
	AdjustWindowRect(&R, WS_OVERLAPPEDWINDOW, false);
	int width = R.right - R.left;
	int height = R.bottom - R.top;
	mhMainWnd = CreateWindow(L"MainWnd", mMainWndCaption.c_str(),
		WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, width, height, nullptr, nullptr, mhAppInst, nullptr);
	if (!mhMainWnd)
	{
		MessageBox(0, L"CreateWindow Failed.", 0, 0);
		return false;
	}

	ShowWindow(mhMainWnd, nCmdShow);
	UpdateWindow(mhMainWnd);

	return true;
}

INT_PTR CALLBACK D3DApp::About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	UNREFERENCED_PARAMETER(lParam);
	switch (message)
	{
	case WM_INITDIALOG:
		return (INT_PTR)TRUE;

	case WM_COMMAND:
		if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
		{
			EndDialog(hDlg, LOWORD(wParam));
			return (INT_PTR)TRUE;
		}
		break;
	}
	return (INT_PTR)FALSE;
}

 

선언부에 앞서 header나 namespace 먼저 간단하게 짚겠습니다.

#include "D3DApp.h"
#include <cassert>

using namespace std;
using namespace Microsoft::WRL;

cassert는 c언어에서 쓰는 assert. 즉, 값을 확인하는 기능을 사용하기 위해 선언 된 것입니다.

기본적인 string 연산이 log에서 사용되므로 std namespace가 선언되어 있고, 

DirectX 기능들을 담당하는 Class들은 Microsoft::WRL로 묶여 있어 이 namespace가 선언되어 있습니다.

 

다음 구현부를 보겠습니다.

LRESULT CALLBACK
MainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	return D3DApp::GetApp()->MsgProc(hwnd, msg, wParam, lParam);
}

D3DApp* D3DApp::mApp = nullptr;
D3DApp* D3DApp::GetApp()
{
	return mApp;
}

D3DApp::D3DApp(HINSTANCE hInstance) : mhAppInst(hInstance)
{
	assert(mApp == nullptr);
	mApp = this;
}

D3DApp::~D3DApp()
{
	if (md3dDevice != nullptr)
	{
		FlushCommandQueue();
	}
}

HINSTANCE D3DApp::AppInst() const
{
	return mhAppInst;
}

HWND D3DApp::MainWnd() const
{
	return mhMainWnd;
}

float D3DApp::AspectRatio() const
{
	return static_cast<float>(mClientWidth) / mClientHeight;
}

bool D3DApp::Get4xMsaaState() const
{
	return m4xMsaaState;
}

void D3DApp::Set4xMsaaState(bool value)
{
	if (m4xMsaaState != value)
	{
		m4xMsaaState = value;
		CreateSwapChain();
		OnResize();
	}
}

최상단의 MainWndProc는 MainWnd라는 Window Programming Main에서의 Callback 함수입니다.

아마 나중에 분리가 될 것 같습니다. 자세한 것은 Window Programming을 공부합시다.

 

이 안에서 Static 변수인 mApp도 초기화가 되고, 생성자와 소멸자가 선언되어 있습니다.

생성자에서는 mApp이 존재하느냐를 확인하고, 존재하면 이를 자기 자신 객체로 담습니다.

전형적인 비 Multi Thread에서의 Singleton 방식 중 하나이죠.

 

소멸자에서는 CommandQueue를 모두 비워줍니다.

그 외에는 대부분 Getter이거나, 간단한 연산만 진행됩니다.

Set4xMsaaState은 4xMsaa를 켰다가 껐을 때 SwapChain을 새로 생성하고 Resize를 실행하는 것입니다.

 

여기 있는 함수들은 생성자와 소멸자는 Singleton을 없애고, 나머지는 모두 분리될 것입니다.

 

다음은 DirectX의 실행을 담당하는 함수들의 구현체입니다.

int D3DApp::run()
{
	MSG msg{ 0 };
	mTimer.Reset();

	while (msg.message != WM_QUIT)
	{
		if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else
		{
			mTimer.Tick();

			if (!mAppPaused)
			{
				CalculateFrameStats();
				Update(mTimer);
				Draw(mTimer);
			}
			else
			{
				Sleep(100);
			}
		}
	}
	return static_cast<int>(msg.wParam);
}

bool D3DApp::Initialize()
{
	if (!InitMainWindow())
	{
		return false;
	}
	if (!InitDirect3D())
	{
		return false;
	}

	OnResize();
	return true;
}

void D3DApp::CreateRtvAndDsvDescriptorHeaps()
{
	D3D12_DESCRIPTOR_HEAP_DESC rtvheapDesc;
	rtvheapDesc.NumDescriptors = SwapChainBufferCount;
	rtvheapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
	rtvheapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
	rtvheapDesc.NodeMask = 0;
	ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&rtvheapDesc, IID_PPV_ARGS(mRtvHeap.GetAddressOf())));

	D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc;
	dsvHeapDesc.NumDescriptors = 1;
	dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
	dsvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
	dsvHeapDesc.NodeMask = 0;
	ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&dsvHeapDesc, IID_PPV_ARGS(mDsvHeap.GetAddressOf())));
}

run은 Model을 실질적으로 실행시키는 함수입니다.

이 부분도 여러 부분이 섞여 있는데, DirectX에서 다룰 부분을 최소화로 줄여보자면 else 안 부분만 포함 될 것입니다.

때문에 이 부분도 나중에 수정을 해줄 예정입니다.

 

Initialize 역시 Window 초기화와 DirectX 초기화가 섞여있기에 차후 수정 될 예정입니다.

 

CreateRtvAndDsvDescriptorHeaps는 Renter Target View, Depth Stencil View의 Descriptor Heap을 생성합니다.

Descriptor Heap은 반드시 초기화 과정에서 생성 되어야 하기 때문에 이 점을 유의함녀 됩니다.

 

이 뒤는 Windows 부분 기능과 좀 더 가까운 구현체들입니다.

void D3DApp::OnResize()
{
	assert(md3dDevice);
	assert(mSwapChain);
	assert(mDirectCmdListAlloc);

	FlushCommandQueue();

	ThrowIfFailed(mCommandList->Reset(mDirectCmdListAlloc.Get(), nullptr));
	
	for (int i = 0; i < SwapChainBufferCount; ++i)
	{
		mSwapChainbuffer[i].Reset();
	}
	mDepthStencilBuffer.Reset();

	ThrowIfFailed(mSwapChain->ResizeBuffers(
		SwapChainBufferCount, 
		mClientWidth, 
		mClientHeight, 
		mBackBufferFormat, 
		DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH));

	mCurrentBackBuffer = 0;

	D3D12_CPU_DESCRIPTOR_HANDLE rtvHeapHandle(mRtvHeap->GetCPUDescriptorHandleForHeapStart());
	for (UINT i = 0; i < SwapChainBufferCount; ++i)
	{
		ThrowIfFailed(mSwapChain->GetBuffer(i, IID_PPV_ARGS(&mSwapChainbuffer[i])));
		md3dDevice->CreateRenderTargetView(mSwapChainbuffer[i].Get(), nullptr, rtvHeapHandle);
		rtvHeapHandle.ptr += mRtvDescriptorSize;
	}

	D3D12_RESOURCE_DESC depthStencilDesc;
	depthStencilDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
	depthStencilDesc.Alignment = 0;
	depthStencilDesc.Width = mClientWidth;
	depthStencilDesc.Height = mClientHeight;
	depthStencilDesc.DepthOrArraySize = 1;
	depthStencilDesc.MipLevels = 1;
	
	depthStencilDesc.Format = DXGI_FORMAT_R24G8_TYPELESS;
	depthStencilDesc.SampleDesc.Count = m4xMsaaState ? 4 : 1;
	depthStencilDesc.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
	depthStencilDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
	depthStencilDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;

	D3D12_CLEAR_VALUE optClear;
	optClear.Format = mDepthStencilFormat;
	optClear.DepthStencil.Depth = 1.f;
	optClear.DepthStencil.Stencil = 0;
	
	D3D12_HEAP_PROPERTIES HeapProperty;
	HeapProperty.Type = D3D12_HEAP_TYPE_DEFAULT;
	HeapProperty.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
	HeapProperty.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
	HeapProperty.CreationNodeMask = 1;
	HeapProperty.VisibleNodeMask = 1;

	ThrowIfFailed(md3dDevice->CreateCommittedResource(
		&HeapProperty, D3D12_HEAP_FLAG_NONE, 
		&depthStencilDesc, D3D12_RESOURCE_STATE_COMMON, 
		&optClear, IID_PPV_ARGS(mDepthStencilBuffer.GetAddressOf())));

	D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc;
	dsvDesc.Flags = D3D12_DSV_FLAG_NONE;
	dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
	dsvDesc.Format = mDepthStencilFormat;
	dsvDesc.Texture2D.MipSlice = 0;
	md3dDevice->CreateDepthStencilView(mDepthStencilBuffer.Get(), &dsvDesc, DepthStencilView());

	D3D12_RESOURCE_BARRIER Barrier;
	ZeroMemory(&Barrier, sizeof(Barrier));
	Barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
	Barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
	Barrier.Transition.pResource = mDepthStencilBuffer.Get();
	Barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COMMON;
	Barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_DEPTH_WRITE;
	mCommandList->ResourceBarrier(1, &Barrier);

	ThrowIfFailed(mCommandList->Close());
	ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };
	mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);

	FlushCommandQueue();

	mScreenViewport.TopLeftX = 0;
	mScreenViewport.TopLeftY = 0;
	mScreenViewport.Width = static_cast<float>(mClientWidth);
	mScreenViewport.Height = static_cast<float>(mClientHeight);
	mScreenViewport.MinDepth = 0.0f;
	mScreenViewport.MaxDepth = 1.0f;

	mScissorRect = { 0, 0, mClientWidth, mClientHeight };
}

LRESULT D3DApp::MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	//WndProc
	switch (msg)
	{
	case WM_COMMAND:
	{
		int wmId = LOWORD(wParam);
		// Parse the menu selections:
		switch (wmId)
		{
		case IDM_ABOUT:
			//DialogBox(mhAppInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, About);
			break;
		case ID_EXAMPLE_CHAPTER6:

			break;
		case IDM_EXIT:
			DestroyWindow(hwnd);
			break;
		default:
			return DefWindowProc(hwnd, msg, wParam, lParam);
		}
	}
		break;
	case WM_PAINT:
	{
		PAINTSTRUCT ps;
		HDC hdc = BeginPaint(hwnd, &ps);
		// TODO: Add any drawing code that uses hdc here...
		EndPaint(hwnd, &ps);
	}
	break;
	case WM_ACTIVATE:
		if (LOWORD(wParam) == WA_INACTIVE)
		{
			mAppPaused = true;
			mTimer.Stop();
		}
		else
		{
			mAppPaused = false;
			mTimer.Start();
		}
		return 0;
	case WM_SIZE:
		mClientWidth = LOWORD(lParam);
		mClientHeight = HIWORD(lParam);
		if (md3dDevice)
		{
			if (wParam == SIZE_MINIMIZED)
			{
				mAppPaused = true;
				mMinimized = true;
				mMaximized = false;
			}
			else if (wParam == SIZE_MAXIMIZED)
			{
				mAppPaused = false;
				mMinimized = false;
				mMaximized = true;
				OnResize();
			}
			else if (wParam == SIZE_RESTORED)
			{
				if (mMinimized)
				{
					mAppPaused = false;
					mMinimized = false;
					OnResize();
				}
				else if (mMaximized)
				{
					mAppPaused = false;
					mMaximized = false;
					OnResize();
				}
				else if (mResizing)
				{

				}
				else
				{
					OnResize();
				}
			}
		}
		return 0;
	case WM_ENTERSIZEMOVE:
		mAppPaused = true;
		mResizing = true;
		mTimer.Stop();
		return 0;
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_MENUCHAR:
		return MAKELRESULT(0, MNC_CLOSE);
	case WM_GETMINMAXINFO:
		((MINMAXINFO*)lParam)->ptMinTrackSize.x = 200;
		((MINMAXINFO*)lParam)->ptMinTrackSize.y = 200;
		return 0;
	case WM_LBUTTONDOWN:
	case WM_MBUTTONDOWN:
	case WM_RBUTTONDOWN:
		OnMouseDown(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
		return 0;
	case WM_LBUTTONUP:
	case WM_MBUTTONUP:
	case WM_RBUTTONUP:
		OnMouseUp(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
		return 0;
	case WM_MOUSEMOVE:
		OnMouseMove(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
		return 0;
	case WM_KEYUP:
		if (wParam == VK_ESCAPE)
		{
			PostQuitMessage(0);
		}
		else if (static_cast<int>(wParam) == VK_F2)
		{
			Set4xMsaaState(!m4xMsaaState);
		}
		return 0;
	}
	return DefWindowProc(hwnd, msg, wParam, lParam);
}

bool D3DApp::InitMainWindow()
{
	WNDCLASSEXW wc;
	if (!MyRegisterClass(wc))
	{
		MessageBox(0, L"RegisterClass Failed.", 0, 0);
		return false;
	}
	return InitInstance(SW_SHOW);
}

어떻게 보면 가장 긴 함수들입니다.

우선 정리를 하면, MsgProc는 Window에서의 이벤트 함수를 연결하는 함수입니다.

무시하면 됩니다.

InitMainWindow는 Window의 초기화 함수입니다.

이 또한 이 기능이 바뀔 일이 거의 없으며, 그대로 분리 될 예정이니 무시하면 됩니다.

 

우리가 볼 것은 OnResize입니다.

OnResize는 화면 크기가 바뀔 때 호출이 됩니다.

이는 매우 간단하게 생각할 문제가 아닙니다.

여태까지 담겨 있던 Command, 모든 View Descriptor, Swap Chain 값들이 모두 바뀌어야 하기 때문입니다.

즉, 모든 것이 flush 되고 reset 되고 다시 생성이 되어야 합니다.

 

그렇기에 assert로 값을 보장하고, Flush 하거나 Reset을 해줍니다.

그 뒤 SwapChain의 크기를 새 화면 크기로 변경하고, RTV, DSV를 새로 생성해준 뒤 Barrier로 마무리한다.

 

사실 이렇게 긴 이유는 MS가 지원해주는 구현체를 사용하지 않기 때문이다.

이를 사용한다면 40줄은 더 줄일 수 있을 것이다.

 

다음 구현체는 DirectX 기능 위주의 구현체이다.

bool D3DApp::InitDirect3D()
{
#if defined(DEBUG) || defined(_DEBUG)
	{
		ComPtr<ID3D12Debug> debugController;
		ThrowIfFailed(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)));
		debugController->EnableDebugLayer();
	}
#endif

	ThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(&mdxgiFactory)));

	HRESULT hardwareResult = D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_12_1, IID_PPV_ARGS(&md3dDevice));

	if (FAILED(hardwareResult))
	{
		ComPtr<IDXGIAdapter4> pWarpAdapter;
		ThrowIfFailed(mdxgiFactory->EnumWarpAdapter(IID_PPV_ARGS(&pWarpAdapter)));

		ThrowIfFailed(D3D12CreateDevice(pWarpAdapter.Get(), D3D_FEATURE_LEVEL_12_1, IID_PPV_ARGS(&md3dDevice)));
	}

	ThrowIfFailed(md3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence)));

	mRtvDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
	mDsvDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
	mCbvSrvUavDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

	D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS msQualityLevels;
	msQualityLevels.Format = mBackBufferFormat;
	msQualityLevels.SampleCount = 4;
	msQualityLevels.Flags = D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE;
	msQualityLevels.NumQualityLevels = 0;
	ThrowIfFailed(md3dDevice->CheckFeatureSupport(D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS, &msQualityLevels, sizeof(msQualityLevels)));

	m4xMsaaQuality = msQualityLevels.NumQualityLevels;
	assert(m4xMsaaQuality > 0 && "Unexpected Msaa quality level.");

#ifdef _DEBUG
	LogAdpaters();
#endif
	
	CreateCommandObjects();
	CreateSwapChain();
	CreateRtvAndDsvDescriptorHeaps();

	return true;
}

void D3DApp::CreateCommandObjects()
{
	D3D12_COMMAND_QUEUE_DESC queueDesc = {};
	queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
	queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
	ThrowIfFailed(md3dDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&mCommandQueue)));

	ThrowIfFailed(md3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(mDirectCmdListAlloc.GetAddressOf())));

	ThrowIfFailed(md3dDevice->CreateCommandList(
		0, 
		D3D12_COMMAND_LIST_TYPE_DIRECT, 
		mDirectCmdListAlloc.Get(), 
		nullptr, 
		IID_PPV_ARGS(mCommandList.GetAddressOf())));

	mCommandList->Close();
}

void D3DApp::CreateSwapChain()
{
	mSwapChain.Reset();
	
	DXGI_SWAP_CHAIN_DESC sd;
	sd.BufferDesc.Width = mClientWidth;
	sd.BufferDesc.Height = mClientHeight;
	sd.BufferDesc.RefreshRate.Numerator = 60;
	sd.BufferDesc.RefreshRate.Denominator = 1;
	sd.BufferDesc.Format = mBackBufferFormat;
	sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
	sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
	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;
	sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
	

	ThrowIfFailed(mdxgiFactory->CreateSwapChain(mCommandQueue.Get(), &sd, mSwapChain.GetAddressOf()));
	//ThrowIfFailed(mdxgiFactory->CreateSwapChainForHwnd(mCommandQueue.Get(), mhMainWnd, &sd1, &swapChainFSDesc, nullptr, &mSwapChain));
	//not recommand to use CreateSwapChain
}

void D3DApp::FlushCommandQueue()
{
	++mCurrentFence;
	
	ThrowIfFailed(mCommandQueue->Signal(mFence.Get(), mCurrentFence));

	if (mFence->GetCompletedValue() < mCurrentFence)
	{
		HANDLE eventHandle = CreateEvent(nullptr, false, false, nullptr);
		//HANDLE eventHandle = CreateEventEx(nullptr, false, false, EVENT_ALL_ACCESS);
		//https://github.com/microsoft/DirectX-Graphics-Samples/issues/15

		ThrowIfFailed(mFence->SetEventOnCompletion(mCurrentFence, eventHandle));

		WaitForSingleObject(eventHandle, INFINITE);
		CloseHandle(eventHandle);
	}
}

InitDirect3D는 DirectX를 사용하기 위한 준비를 하는 함수이다.

우선 DirectX 기능을 사용하기 위한 기본 객체인 D3D12Device를 생성하고, 값을 검증 한 뒤 이를 기반으로 RTV, DSV,

CBVSRVUAV (Constant Buffer View, Shader Resource View, Unordered Access View) Descriptor Heap Size를 저장한다.

그리고 QualityLevel을 설정하고, 4xMsaaQuality를 설정한다.

마지막으로 CommandObject, SwapChain, DescriptorHeaps를 생성한다.

이 중 Descriptorheaps 생성 함수는 위에서 보았고, 먼저 CreateCommandObject부터 보자.

 

CreateCommandObject에서는 CommandQueue를 생성해 그 속성을 지정하고, CommandQueue를 생성한다.

그 다음 비어있는 Allocator를 생성해 앞서 생성한 CommandQueue와 속성을 동일하게 맞추고, 

CommandList를 생성하면서 Allocator와 CommandList를 연결한다.

그리고 CommandList를 닫아서 수정 단계를 끝낸다.

 

CreateSwapChain은 더 간단하다.

기존의 SwapChain을 모두 비워주고,

필요한 설정을 구조체에 채워넣고 CreateSwapChain을 선언해서 새로운 SwapChain을 생성한다.

 

마지막으로 FlushCommandQueue는 솔직히 어떻게 움직이는지 잘 모르겠습니다.

나중에 이해가 가면 수정을 하겠습니다.

 

위에서 핵심적인 코드를 주로 살펴보았으니 비교적 덜 코어한 코드를 살펴봅시다.

ID3D12Resource* D3DApp::CurrentBackBuffer() const
{
	return mSwapChainbuffer[mCurrentBackBuffer].Get();
}

D3D12_CPU_DESCRIPTOR_HANDLE D3DApp::CurrentBackBufferView() const
{
	D3D12_CPU_DESCRIPTOR_HANDLE result(mRtvHeap->GetCPUDescriptorHandleForHeapStart());
	result.ptr += mCurrentBackBuffer * mRtvDescriptorSize;
	return result;
}

D3D12_CPU_DESCRIPTOR_HANDLE D3DApp::DepthStencilView() const
{
	return mDsvHeap->GetCPUDescriptorHandleForHeapStart();
}

void D3DApp::CalculateFrameStats()
{
	static int frameCnt = 0;
	static float timeElapse = 0.f;

	++frameCnt;

	if ((mTimer.TotalTime() - timeElapse) >= 1.f)
	{
		float fps = static_cast<float>(frameCnt);
		float mspf = 1000.f / fps;
		
		wstring fpsStr = to_wstring(fps);
		wstring mspStr = to_wstring(mspf);

		wstring windowText = mMainWndCaption + L",    fps: " + fpsStr + L"ms,    pf: " + mspStr;

		SetWindowText(mhMainWnd, windowText.c_str());
		frameCnt = 0;
		timeElapse += 1.f;
	}
}

void D3DApp::LogAdpaters()
{
	UINT i = 0;
	IDXGIAdapter* adapter = nullptr;
	std::vector<IDXGIAdapter*> adapterList;
	while (mdxgiFactory->EnumAdapters(i, &adapter) != DXGI_ERROR_NOT_FOUND)
	{
		DXGI_ADAPTER_DESC desc;
		adapter->GetDesc(&desc);

		std::wstring text = L"***Adpater: ";
		text += desc.Description;
		text += L"\n";

		OutputDebugString(text.c_str());

		adapterList.push_back(adapter);

		++i;
	}

	for (size_t i = 0; i < adapterList.size(); ++i)
	{
		LogAdpaterOutputs(adapterList[i]);
		if (adapterList[i])
		{
			adapterList[i]->Release();
			adapterList[i] = 0;
		}
	}
}

void D3DApp::LogAdpaterOutputs(IDXGIAdapter* adapter)
{
	UINT i = 0;
	IDXGIOutput* output = nullptr;
	while (adapter->EnumOutputs(i, &output) != DXGI_ERROR_NOT_FOUND)
	{
		DXGI_OUTPUT_DESC desc;
		output->GetDesc(&desc);

		std::wstring text = L"***Output: ";
		text += desc.DeviceName;
		text += L"\n";
		OutputDebugString(text.c_str());

		LogOutputDisplayModes(output, mBackBufferFormat);

		if (output)
		{
			output->Release();
			output = 0;
		}

		++i;
	}
}

void D3DApp::LogOutputDisplayModes(IDXGIOutput* output, DXGI_FORMAT format)
{
	UINT count = 0;
	UINT flags = 0;

	output->GetDisplayModeList(format, flags, &count, nullptr);

	std::vector<DXGI_MODE_DESC> modeList(count);
	output->GetDisplayModeList(format, flags, &count, &modeList[0]);

	for (const auto& x : modeList)
	{
		UINT n = x.RefreshRate.Numerator;
		UINT d = x.RefreshRate.Denominator;
		std::wstring text =
			L"Width = " + to_wstring(x.Width) + L" " +
			L"Height = " + to_wstring(x.Height) + L" " +
			L"Refresh = " + to_wstring(n) + L"/" + to_wstring(d) + L"\n";

		::OutputDebugString(text.c_str());
	}
}

단순 연산이나 Getter, 혹은 Log를 출력하는 함수들입니다.

그냥 그렇다 하고 넘어갑시다.

 

마지막 대미를 장식하는건 분리되어야 할 Window 관련 함수입니다.

//
//  FUNCTION: MyRegisterClass()
//
//  PURPOSE: Registers the window class.
//
ATOM D3DApp::MyRegisterClass(WNDCLASSEXW& wc)
{
	wc.cbSize = sizeof(WNDCLASSEX);
	wc.style = CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc = MainWndProc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = mhAppInst;
	wc.hIcon = LoadIcon(mhAppInst, MAKEINTRESOURCE(IDI_DIRECTX12EXAMPLECODE));
	wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
	wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wc.lpszMenuName = MAKEINTRESOURCEW(IDC_DIRECTX12EXAMPLECODE);
	wc.lpszClassName = L"MainWnd";
	wc.hIconSm = LoadIcon(wc.hInstance, MAKEINTRESOURCE(IDI_SMALL));
	return RegisterClassExW(&wc);
}

//
//   FUNCTION: InitInstance(HINSTANCE, int)
//
//   PURPOSE: Saves instance handle and creates main window
//
//   COMMENTS:
//
//        In this function, we save the instance handle in a global variable and
//        create and display the main program window.
//
BOOL D3DApp::InitInstance(int nCmdShow)
{
	RECT R = { 0, 0, mClientWidth, mClientHeight };
	AdjustWindowRect(&R, WS_OVERLAPPEDWINDOW, false);
	int width = R.right - R.left;
	int height = R.bottom - R.top;
	mhMainWnd = CreateWindow(L"MainWnd", mMainWndCaption.c_str(),
		WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, width, height, nullptr, nullptr, mhAppInst, nullptr);
	if (!mhMainWnd)
	{
		MessageBox(0, L"CreateWindow Failed.", 0, 0);
		return false;
	}

	ShowWindow(mhMainWnd, nCmdShow);
	UpdateWindow(mhMainWnd);

	return true;
}

INT_PTR CALLBACK D3DApp::About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	UNREFERENCED_PARAMETER(lParam);
	switch (message)
	{
	case WM_INITDIALOG:
		return (INT_PTR)TRUE;

	case WM_COMMAND:
		if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
		{
			EndDialog(hDlg, LOWORD(wParam));
			return (INT_PTR)TRUE;
		}
		break;
	}
	return (INT_PTR)FALSE;
}

바뀔 일 없고, 수정 시 분리 될 기능들입니다.

DirectX와 직접 관련이 있는것은 아니니 넘어가도 되지 않나 싶습니다.

 

코드 양에 비해 설명이 적을 수 있습니다.

하지만 다음에 적을 글과 이어진다면 대략적인 흐름은 파악 할 수 있을 것입니다.

 

다음에 작성할 때에는 BoxApp과 Main 함수를 작성하겠습니다.

'내용정리 > DirectX12' 카테고리의 다른 글

20.09.18 개발일지  (0) 2020.09.18
10. Box Example 3 - BoxApp, WinMain  (0) 2020.09.15
10. Box Example 1  (0) 2020.09.04
20.08.25 개발일지  (0) 2020.08.25
20.08.21 개발일지  (0) 2020.08.21

이번에는 Chapter 6의 Box Example을

VS에서 제공하는 Windows Programming Template에 적용한 코드를 소개하려 합니다.

Project Introduction

 

Box Example Project

Common 폴더 안의 코드들은 DirectX를 사용하는데 필요한 몇가지 코드들을 모아놓은 것입니다.

즉, 책에서 작성되고, 사용된 코드들의 집합체라는 것입니다.

 

실질적인 구현체인 BoxApp은 Example 폴더 안에 있습니다.

앞으로 추가적인 예시 코드들은 Example 폴더 안에 작성될 예정입니다.

 

폴더에 들어있지 않은 코드 중 DirectX12ExampleCode는 Main 함수가 적혀있습니다.

이 외에는 Window Programming Template에서 제공된 Header들로,

여기에 예시 작성에 필요한 헤더나 선언들을 몇가지 추가 했습니다.

 

마지막으로 ReosurceFiles은 마찬가지로 Template에서 제공된 것들입니다.

앞으로도 신경쓰지 않으면 됩니다.

 

targetver.h

#pragma once

// Including SDKDDKVer.h defines the highest available Windows platform.
// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and
// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h.
#include <SDKDDKVer.h>

항목을 따로 두긴 했지만, 설명에 적힌것과 마찬가지로 Windows platform 버전을 지정하는 것입니다.

앞으로도 이 버전은 따로 건드릴 예정이 없으니 그대로 방치하면 됩니다.

resource.h

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by DirectX12ExampleCode.rc
//
#define IDC_MYICON                      2
#define IDD_DIRECTX12EXAMPLECODE_DIALOG 102
#define IDS_APP_TITLE                   103
#define IDD_ABOUTBOX                    103
#define IDM_ABOUT                       104
#define IDM_EXIT                        105
#define IDI_DIRECTX12EXAMPLECODE        107
#define IDI_SMALL                       108
#define IDC_DIRECTX12EXAMPLECODE        109
#define IDR_MAINFRAME                   128
#define ID_EXAMPLE_CHAPTER6             32771
#define IDC_STATIC                      -1

// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC                     1
#define _APS_NEXT_RESOURCE_VALUE        129
#define _APS_NEXT_COMMAND_VALUE         32774
#define _APS_NEXT_CONTROL_VALUE         1000
#define _APS_NEXT_SYMED_VALUE           110
#endif
#endif

Window에 사용되는 Resource들에 대한 키 값이 지정되어 있는 곳입니다.

여기서 눈여겨 봐야 할 점이라면 ID_EXAMPLE_CHAPTER6입니다.

이는 Example 메뉴에서 Chapter 6를 클릭했을 시 Box Example이 출력되게 하기 위해 추가된 것입니다.

다만 아직 이 기능까지 구현을 하지 못했고, 구조상 아직은 런타임상에서 교체를 할 수 없습니다.

그러니 예시가 늘어날 때마다 새로운 항목만 추가를 합시다.

framework.h

// header.h : include file for standard system include files,
// or project specific include files
//

#pragma once

#include "targetver.h"
#define WIN32_LEAN_AND_MEAN             // Exclude rarely-used stuff from Windows headers
// Windows Header Files
#include <windows.h>
// C RunTime Header Files
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <tchar.h>

Template상에서 필요한 헤더들이 선언된 곳입니다.

그즉슨, Window 제작에 필요한 것들이 선언되었다는 것입니다.

아직 완벽히 작성되지 않았지만, 앞으로 Window 제작에 필요한 헤더는 다 이곳으로 이동될 것입니다.

하지만 틀만 완성되면 그 이후로는 DirectX 구현이라 이부분은 크게 게의치 않아도 될 것 같습니다.

 

Common

DXException

#pragma once
#include <Windows.h>
#include <string>

class DXException
{
public:
	DXException();
	DXException(HRESULT hr, const std::wstring& functionName, const std::wstring& filename, int lineNumber);

	std::wstring ToString() const;

	HRESULT ErrorCode = S_OK;
	std::wstring FunctionName;
	std::wstring FileName;
	int LineNumber = -1;
};
#include <comdef.h>
#include "DXException.h"

DXException::DXException(HRESULT hr, const std::wstring& functionName, const std::wstring& filename, int lineNumber)
    : ErrorCode(hr), FunctionName(functionName), FileName(filename), LineNumber(lineNumber)
{

}

std::wstring DXException::ToString() const
{
    _com_error err(ErrorCode);
    std::wstring msg = err.ErrorMessage();

    return FunctionName + L" failed in " + FileName + L"; line " + std::to_wstring(LineNumber) + L"; error: " + msg;
}

DXException은 DirectX Example 작성에 있어서 발생하는 에러를 새로운 Window에 출력하는 역할을 한다.

이는 따로 제공하는 코드는 아니고, 책에서 따로 작성한 코드이다.

이는 HRESULT 값을 통해 오류가 발생한 함수 명, 호출된 파일 명, 줄 번호를 담고 있다.

 

여기서 comdef.h라는 낯선 헤더가 눈에 띌 것이다.

이는 ToString에서 사용하는 _com_error 클래스를 담고 있으며, COM types에서 error type을 정의한다.

https://docs.microsoft.com/ko-kr/cpp/cpp/compiler-com-support-classes?view=vs-2019

 

컴파일러 COM 지원 클래스

 

docs.microsoft.com

GameTimer

#ifndef GAMETIMER_H
#define GAMETIMER_H

class GameTimer
{
public:
	GameTimer();

	float TotalTime()const; // in seconds
	float DeltaTime()const; // in seconds

	void Reset(); // Call before message loop.
	void Start(); // Call when unpaused.
	void Stop();  // Call when paused.
	void Tick();  // Call every frame.

private:
	double mSecondsPerCount;
	double mDeltaTime;

	__int64 mBaseTime;
	__int64 mPausedTime;
	__int64 mStopTime;
	__int64 mPrevTime;
	__int64 mCurrTime;

	bool mStopped;
};

#endif // GAMETIMER_H
#include <Windows.h>
#include <memory>
#include "GameTimer.h"

GameTimer::GameTimer() 
	: mSecondsPerCount(0.0), mDeltaTime(0.0), mBaseTime(0), mStopTime(0),
	mPausedTime(0), mPrevTime(0), mCurrTime(0), mStopped(false)
{
	__int64 countsPerSec;
	QueryPerformanceFrequency((LARGE_INTEGER*)&countsPerSec);
	mSecondsPerCount = 1 / static_cast<double>(countsPerSec);
}

float GameTimer::TotalTime() const
{
	return static_cast<float>((((mStopped ? mStopTime : mCurrTime) - mPausedTime) - mBaseTime) * mSecondsPerCount);
}

float GameTimer::DeltaTime() const
{
	return static_cast<float>(mDeltaTime);
}

void GameTimer::Reset()
{
	__int64 currTime;
	QueryPerformanceCounter((LARGE_INTEGER*)&currTime);

	mBaseTime = currTime;
	mPrevTime = currTime;
	mStopTime = 0;
	mStopped = false;
}

void GameTimer::Start()
{
	__int64 startTime;
	QueryPerformanceCounter((LARGE_INTEGER*)&startTime);
	if (mStopped)
	{
		mPausedTime += (startTime - mStopTime);
		
		mPrevTime = startTime;
		mStopTime = 0;
		mStopped = false;
	}
}

void GameTimer::Stop()
{
	if (!mStopped)
	{
		__int64 currTime;
		QueryPerformanceCounter((LARGE_INTEGER*)(&currTime));

		mStopTime = currTime;
		mStopped = true;
	}
}

void GameTimer::Tick()
{
	if (mStopped)
	{
		mDeltaTime = 0.0;
		return;
	}

	__int64 currTime;
	QueryPerformanceCounter((LARGE_INTEGER*)&currTime);
	mCurrTime = currTime;

	mDeltaTime = (mCurrTime - mPrevTime) * mSecondsPerCount;

	mPrevTime = mCurrTime;

	if (mDeltaTime < 0.0)
	{
		mDeltaTime = 0.0;
	}
}

 

GameTimer는 DirectX 예시에서 시간과 관련된 기능들이 구현되어 있습니다.

게임 개발 할 때 초당 Update를 처리하는 Tick이라던가.

이 Tick이 호출되는 주기라던가. 경과 시간이라던가.

 

GameTimer 또한 지원해주는 함수는 아닙니다.

VS에서 지원하는 DirectX12 Template에서는 Windows에서 사용하는 Time 관련 class를 사용하기 때문이죠.

하지만 그쪽 예시는 기본적으로 GPU 사용과 Multicore를 기본 전제로 깔면서,

Windows App Programming에서 요구하는 최신 문법들을 사용하기 때문에 입문을 하는데에 큰 어려움이 따릅니다.

때문에 가능하면 이 함수를 따로 구현하는 것을 추천합니다.

 

대부분의 함수 기능과 변수 역할은 코드를 읽으면 충분히 이해가 갈 것입니다.

그 중 낯선 것이 있다면 QueryPerformanceCounter, QueryPerformanceFrequency인데, 

이는 고해상도 타이머의 Clock수와 주파수를 받아오는 함수입니다.

대체로 CPU의 것을 주로 사용합니다.

http://www.tipssoft.com/bulletin/board.php?bo_table=FAQ&wr_id=735

 

팁스소프트 > MFC/API 가이드 > [Tip] QueryPerformanceCounter 함수에 대하여...

팁스소프트에서 제공하는 프로그래밍과 관련된 자료나 정보들을 무단으로 복제하거나 게재하는 행위는상호간의 신뢰를 무너뜨리는 행위이며, 법적인 문제를 야기할 수 있으므로 각별한 주의��

www.tipssoft.com

Common

#pragma comment(lib, "d3dcompiler.lib")
#pragma comment(lib, "D3D12.lib")
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "dxcompiler.lib")

#pragma once
#include <Windows.h>
#include <wrl.h>
#include <windowsx.h>
#include <d3d12.h>
#include <dxgi1_6.h>
#include <d3dcompiler.h>
#include <dxcapi.h>
#include <DirectXMath.h>
#include <pix.h>
#include <DirectXPackedVector.h>
#include <DirectXColors.h>
#include <DirectXCollision.h>
#include <string>
#include <vector>
#include <array>
#include <unordered_map>
#include <algorithm>
#include <memory>
#include "GameTimer.h"
#include "DXException.h"
#include "../resource.h"

#include <iostream>

#if defined(DEBUG) || defined(_DEBUG)
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
#endif

#define MAX_LOADSTRING 100

// Global Variables:
//HINSTANCE hInst;                                // current instance
//WCHAR szTitle[MAX_LOADSTRING];                  // The title bar text
//WCHAR szWindowClass[MAX_LOADSTRING];            // the main window class name

inline std::wstring AnsiToWString(const std::string& str)
{
	WCHAR buffer[512];
	MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, buffer, 512);
	return std::wstring(buffer);
}

#ifndef ThrowIfFailed
#define ThrowIfFailed(x)							\
{													\
	HRESULT hr__ = (x);								\
	std::wstring wfn = AnsiToWString(__FILE__);		\
	if(FAILED(hr__))								\
	{												\
		throw DXException(hr__, L#x, wfn, __LINE__);\
	}												\
}
#endif

template<typename T>
static T Clamp(const T& x, const T& low, const T& high)
{
	return x < low ? low : (x > high ? high : x);
}

static UINT calcConstantBufferByteSize(UINT byteSize)
{
	return (byteSize + 255) & ~255;
}

static Microsoft::WRL::ComPtr<ID3DBlob> CompileShader(
	const std::wstring& filename,
	const D3D_SHADER_MACRO* defines,
	const std::string& entrypoint,
	const std::string& target)
{
	UINT compileFlags = 0;

#if defined(DEBUG) || defined(_DEBUG)
	compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#endif

	HRESULT hr = S_OK;

	Microsoft::WRL::ComPtr<ID3DBlob> byteCode = nullptr;
	Microsoft::WRL::ComPtr<ID3DBlob> errors = nullptr;

	hr = D3DCompileFromFile(filename.c_str(), defines, D3D_COMPILE_STANDARD_FILE_INCLUDE, 
		entrypoint.c_str(), target.c_str(), compileFlags, 0, &byteCode, &errors);

	if (errors != nullptr)
	{
		OutputDebugStringA(static_cast<char*>(errors->GetBufferPointer()));
	}

	ThrowIfFailed(hr);

	return byteCode;
}


//------------------------------------------------------------------------------------------------
// Row-by-row memcpy
inline void MemcpySubresource(
	_In_ const D3D12_MEMCPY_DEST* pDest,
	_In_ const D3D12_SUBRESOURCE_DATA* pSrc,
	SIZE_T RowSizeInBytes,
	UINT NumRows,
	UINT NumSlices)
{
	for (UINT z = 0; z < NumSlices; ++z)
	{
		BYTE* pDestSlice = reinterpret_cast<BYTE*>(pDest->pData) + pDest->SlicePitch * z;
		const BYTE* pSrcSlice = reinterpret_cast<const BYTE*>(pSrc->pData) + pSrc->SlicePitch * z;
		for (UINT y = 0; y < NumRows; ++y)
		{
			memcpy(pDestSlice + pDest->RowPitch * y,
				pSrcSlice + pSrc->RowPitch * y,
				RowSizeInBytes);
		}
	}
}


//------------------------------------------------------------------------------------------------
// All arrays must be populated (e.g. by calling GetCopyableFootprints)
inline UINT64 UpdateSubresources(
	_In_ ID3D12GraphicsCommandList* pCmdList,
	_In_ ID3D12Resource* pDestinationResource,
	_In_ ID3D12Resource* pIntermediate,
	_In_range_(0, D3D12_REQ_SUBRESOURCES) UINT FirstSubresource,
	_In_range_(0, D3D12_REQ_SUBRESOURCES - FirstSubresource) UINT NumSubresources,
	UINT64 RequiredSize,
	_In_reads_(NumSubresources) const D3D12_PLACED_SUBRESOURCE_FOOTPRINT* pLayouts,
	_In_reads_(NumSubresources) const UINT* pNumRows,
	_In_reads_(NumSubresources) const UINT64* pRowSizesInBytes,
	_In_reads_(NumSubresources) const D3D12_SUBRESOURCE_DATA* pSrcData)
{
	// Minor validation
	D3D12_RESOURCE_DESC IntermediateDesc = pIntermediate->GetDesc();
	D3D12_RESOURCE_DESC DestinationDesc = pDestinationResource->GetDesc();
	if (IntermediateDesc.Dimension != D3D12_RESOURCE_DIMENSION_BUFFER ||
		IntermediateDesc.Width < RequiredSize + pLayouts[0].Offset ||
		RequiredSize >(SIZE_T) - 1 ||
		(DestinationDesc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER &&
			(FirstSubresource != 0 || NumSubresources != 1)))
	{
		return 0;
	}

	BYTE* pData;
	HRESULT hr = pIntermediate->Map(0, NULL, reinterpret_cast<void**>(&pData));
	if (FAILED(hr))
	{
		return 0;
	}

	for (UINT i = 0; i < NumSubresources; ++i)
	{
		if (pRowSizesInBytes[i] > (SIZE_T)-1) return 0;
		D3D12_MEMCPY_DEST DestData = { pData + pLayouts[i].Offset, pLayouts[i].Footprint.RowPitch, pLayouts[i].Footprint.RowPitch * pNumRows[i] };
		MemcpySubresource(&DestData, &pSrcData[i], (SIZE_T)pRowSizesInBytes[i], pNumRows[i], pLayouts[i].Footprint.Depth);
	}
	pIntermediate->Unmap(0, NULL);

	if (DestinationDesc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER)
	{
		D3D12_BOX SrcBox{ static_cast<UINT>(pLayouts[0].Offset), 0, 0, static_cast<UINT>(pLayouts[0].Offset + pLayouts[0].Footprint.Width), 1, 1 };
		//CD3DX12_BOX SrcBox(UINT(pLayouts[0].Offset), UINT(pLayouts[0].Offset + pLayouts[0].Footprint.Width));
		pCmdList->CopyBufferRegion(
			pDestinationResource, 0, pIntermediate, pLayouts[0].Offset, pLayouts[0].Footprint.Width);
	}
	else
	{
		for (UINT i = 0; i < NumSubresources; ++i)
		{
			D3D12_TEXTURE_COPY_LOCATION Dst{ pDestinationResource, D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT, i + FirstSubresource };
			D3D12_TEXTURE_COPY_LOCATION Src{ pIntermediate, D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT, pLayouts[i] };
			//CD3DX12_TEXTURE_COPY_LOCATION Dst(pDestinationResource, i + FirstSubresource);
			//CD3DX12_TEXTURE_COPY_LOCATION Src(pIntermediate, pLayouts[i]);
			pCmdList->CopyTextureRegion(&Dst, 0, 0, 0, &Src, nullptr);
		}
	}
	return RequiredSize;
}

//------------------------------------------------------------------------------------------------
// Heap-allocating UpdateSubresources implementation
inline UINT64 UpdateSubresources(
	_In_ ID3D12GraphicsCommandList* pCmdList,
	_In_ ID3D12Resource* pDestinationResource,
	_In_ ID3D12Resource* pIntermediate,
	UINT64 IntermediateOffset,
	_In_range_(0, D3D12_REQ_SUBRESOURCES) UINT FirstSubresource,
	_In_range_(0, D3D12_REQ_SUBRESOURCES - FirstSubresource) UINT NumSubresources,
	_In_reads_(NumSubresources) D3D12_SUBRESOURCE_DATA* pSrcData)
{
	UINT64 RequiredSize = 0;
	UINT64 MemToAlloc = static_cast<UINT64>(sizeof(D3D12_PLACED_SUBRESOURCE_FOOTPRINT) + sizeof(UINT) + sizeof(UINT64)) * NumSubresources;
	if (MemToAlloc > SIZE_MAX)
	{
		return 0;
	}
	void* pMem = HeapAlloc(GetProcessHeap(), 0, static_cast<SIZE_T>(MemToAlloc));
	if (pMem == NULL)
	{
		return 0;
	}
	D3D12_PLACED_SUBRESOURCE_FOOTPRINT* pLayouts = reinterpret_cast<D3D12_PLACED_SUBRESOURCE_FOOTPRINT*>(pMem);
	UINT64* pRowSizesInBytes = reinterpret_cast<UINT64*>(pLayouts + NumSubresources);
	UINT* pNumRows = reinterpret_cast<UINT*>(pRowSizesInBytes + NumSubresources);

	D3D12_RESOURCE_DESC Desc = pDestinationResource->GetDesc();
	ID3D12Device* pDevice;
	pDestinationResource->GetDevice(__uuidof(*pDevice), reinterpret_cast<void**>(&pDevice));
	pDevice->GetCopyableFootprints(&Desc, FirstSubresource, NumSubresources, IntermediateOffset, pLayouts, pNumRows, pRowSizesInBytes, &RequiredSize);
	pDevice->Release();

	UINT64 Result = UpdateSubresources(pCmdList, pDestinationResource, pIntermediate, FirstSubresource, NumSubresources, RequiredSize, pLayouts, pNumRows, pRowSizesInBytes, pSrcData);
	HeapFree(GetProcessHeap(), 0, pMem);
	return Result;
}

static Microsoft::WRL::ComPtr<ID3D12Resource> CreateDefaultbuffer(
	ID3D12Device* device,
	ID3D12GraphicsCommandList* cmdList,
	const void* initData,
	UINT64 byteSize,
	Microsoft::WRL::ComPtr<ID3D12Resource>& uploadBuffer)
{
	Microsoft::WRL::ComPtr<ID3D12Resource> defaultBuffer;

	D3D12_HEAP_PROPERTIES BeforeDefaultHeapProperty{ D3D12_HEAP_TYPE_DEFAULT, D3D12_CPU_PAGE_PROPERTY_UNKNOWN, D3D12_MEMORY_POOL_UNKNOWN, 1, 1 };
	D3D12_RESOURCE_DESC BeforeDefaultResource{
		D3D12_RESOURCE_DIMENSION_BUFFER, 
		0, byteSize, 1, 1, 1, 
		DXGI_FORMAT_UNKNOWN, 1, 0, 
		D3D12_TEXTURE_LAYOUT_ROW_MAJOR, 
		D3D12_RESOURCE_FLAG_NONE };
	ThrowIfFailed(device->CreateCommittedResource(
		&BeforeDefaultHeapProperty,
		D3D12_HEAP_FLAG_NONE,
		&BeforeDefaultResource,
		D3D12_RESOURCE_STATE_COMMON,
		nullptr,
		IID_PPV_ARGS(defaultBuffer.GetAddressOf())
	));

	D3D12_HEAP_PROPERTIES AfterUploadHeapProperty{ D3D12_HEAP_TYPE_UPLOAD, D3D12_CPU_PAGE_PROPERTY_UNKNOWN, D3D12_MEMORY_POOL_UNKNOWN, 1, 1 };
	D3D12_RESOURCE_DESC AfterUploadResource{
		D3D12_RESOURCE_DIMENSION_BUFFER,
		0, byteSize, 1, 1, 1,
		DXGI_FORMAT_UNKNOWN, 1, 0,
		D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
		D3D12_RESOURCE_FLAG_NONE };
	ThrowIfFailed(device->CreateCommittedResource(
		&AfterUploadHeapProperty,
		D3D12_HEAP_FLAG_NONE,
		&AfterUploadResource,
		D3D12_RESOURCE_STATE_GENERIC_READ,
		nullptr,
		IID_PPV_ARGS(uploadBuffer.GetAddressOf())
	));

	D3D12_SUBRESOURCE_DATA subResourceData = {};
	subResourceData.pData = initData;
	subResourceData.RowPitch = static_cast<LONG_PTR>(byteSize);
	subResourceData.SlicePitch = subResourceData.RowPitch;



	D3D12_RESOURCE_BARRIER BeforeBarrier;
	ZeroMemory(&BeforeBarrier, sizeof(BeforeBarrier));
	BeforeBarrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
	BeforeBarrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
	BeforeBarrier.Transition.pResource = defaultBuffer.Get();
	BeforeBarrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COMMON;
	BeforeBarrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST;
	BeforeBarrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
	cmdList->ResourceBarrier(1, &BeforeBarrier);

	UpdateSubresources(cmdList, defaultBuffer.Get(), uploadBuffer.Get(), 0, 0, 1, &subResourceData);

	D3D12_RESOURCE_BARRIER AfterBarrier;
	ZeroMemory(&AfterBarrier, sizeof(AfterBarrier));
	AfterBarrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
	AfterBarrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
	AfterBarrier.Transition.pResource = defaultBuffer.Get();
	AfterBarrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
	AfterBarrier.Transition.StateAfter = D3D12_RESOURCE_STATE_GENERIC_READ;
	AfterBarrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
	cmdList->ResourceBarrier(1, &AfterBarrier);

	return defaultBuffer;
}

Common.h는 크게 3가지 기능을 가지고 있습니다.첫번째는 DirectX Library의 링크. 가장 위의 4개 줄이 이 역할을 맡습니다.두번째는 DirectX 예시 작성에 필요한 Header Include.세번째는 DirectX 예시 전반적으로 요구되는 공통 함수들이 선언되어 있습니다.원래 예시에서는 D3DUtill이었으며, 위에서 설명한 DXException이나 GameTimer도 원래 이 안에 선언되어 있었습니다.하지만 Class 단위의 것들은 별도의 파일로 분리를 하면서 따로 작성이 되었습니다.

 

우선은 이대로 사용을 하고, 어느정도 추가 될 내용이 없어지면 다시 한번 정리를 할 예정입니다.

MeshGeometry

#pragma once
#include "Common.h"

struct SubmeshGeometry
{
	UINT IndexCount = 0;
	UINT StartIndexLocation = 0;
	INT BaseVertexLocation = 0;

	DirectX::BoundingBox Bounds;
};

struct MeshGeometry
{
	std::string Name;

	Microsoft::WRL::ComPtr<ID3DBlob> VertexBufferCPU = nullptr;
	Microsoft::WRL::ComPtr<ID3DBlob> IndexBufferCPU = nullptr;

	Microsoft::WRL::ComPtr<ID3D12Resource> VertexBufferGPU = nullptr;
	Microsoft::WRL::ComPtr<ID3D12Resource> IndexBufferGPU = nullptr;

	Microsoft::WRL::ComPtr<ID3D12Resource> VertexBufferUploader = nullptr;
	Microsoft::WRL::ComPtr<ID3D12Resource> IndexBufferUploader = nullptr;

	UINT VertexByteStride = 0;
	UINT VertexBufferByteSize = 0;
	DXGI_FORMAT IndexFormat = DXGI_FORMAT_R16_UINT;
	UINT IndexBufferByteSize = 0;

	std::unordered_map<std::string, SubmeshGeometry> DrawArgs;

	D3D12_VERTEX_BUFFER_VIEW VertexBufferView() const
	{
		D3D12_VERTEX_BUFFER_VIEW vbv;
		vbv.BufferLocation = VertexBufferGPU->GetGPUVirtualAddress();
		vbv.StrideInBytes = VertexByteStride;
		vbv.SizeInBytes = VertexBufferByteSize;

		return vbv;
	}
	
	D3D12_INDEX_BUFFER_VIEW IndexBufferView() const
	{
		D3D12_INDEX_BUFFER_VIEW ibv;
		ibv.BufferLocation = IndexBufferGPU->GetGPUVirtualAddress();
		ibv.Format = IndexFormat;
		ibv.SizeInBytes = IndexBufferByteSize;

		return ibv;
	}
};

MeshGeometry는 DirectX 예시 코드에서 사용하는 Model 객체입니다.

Resource를 읽고 이를 기반으로 Vertex, Index를 내부에서 관리하면서 BufferView를 제공합니다.

간단하게 "Resource를 이렇게 관리한다."와 "이러이러한 정보들을 받아올 수 있다."를 파악하면 될 것 같습니다.

UploadBuffer

#pragma once
#include "Common.h"

template<typename T>
class UploadBuffer
{
public:
	UploadBuffer(ID3D12Device* device, UINT elementCount, bool isConstantBuffer) : mIsConstantBuffer(isConstantBuffer)
	{
		mElementByteSize = sizeof(T);
		
		if (isConstantBuffer)
		{
			mElementByteSize = calcConstantBufferByteSize(sizeof(T));
		}
		D3D12_HEAP_PROPERTIES HeapProperty;
		HeapProperty.Type = D3D12_HEAP_TYPE_UPLOAD;
		HeapProperty.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
		HeapProperty.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
		HeapProperty.CreationNodeMask = 1;
		HeapProperty.VisibleNodeMask = 1;

		D3D12_RESOURCE_DESC Resource;
		Resource.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
		Resource.Alignment = 0;
		Resource.Width = mElementByteSize * static_cast<UINT64>(elementCount);
		Resource.Height = 1;
		Resource.DepthOrArraySize = 1;
		Resource.MipLevels = 1;
		Resource.Format = DXGI_FORMAT_UNKNOWN;
		Resource.SampleDesc.Count = 1;
		Resource.SampleDesc.Quality = 0;
		Resource.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
		Resource.Flags = D3D12_RESOURCE_FLAG_NONE;

		ThrowIfFailed(device->CreateCommittedResource(
			&HeapProperty,
			D3D12_HEAP_FLAG_NONE,
			&Resource,
			D3D12_RESOURCE_STATE_GENERIC_READ,
			nullptr,
			IID_PPV_ARGS(&mUploadBuffer)
		));

		ThrowIfFailed(mUploadBuffer->Map(0, nullptr, reinterpret_cast<void**>(&mMappedData)));
	}
	UploadBuffer(const UploadBuffer& rhs) = delete;
	UploadBuffer& operator=(const UploadBuffer& rhs) = delete;
	~UploadBuffer()
	{
		if (mUploadBuffer != nullptr)
		{
			mUploadBuffer->Unmap(0, nullptr);
		}
		mMappedData = nullptr;
	}

	ID3D12Resource* Resource() const
	{
		return mUploadBuffer.Get();
	}

	void CopyData(int elementIndex, const T& data)
	{
		memcpy(&mMappedData[elementIndex * mElementByteSize], &data, sizeof(T));
	}

private:
	Microsoft::WRL::ComPtr<ID3D12Resource> mUploadBuffer;
	BYTE* mMappedData = nullptr;

	UINT mElementByteSize = 0;
	bool mIsConstantBuffer = false;
};

UploadBuffer는 DirectX에서 사용하는 Buffer의 기본적인 기능을 구현한 것입니다.

복사생성자와 대입연산자 생성을 막아서 복사를 하지 못하게 하였고,

별도의 데이터 복사나 버퍼의 포인터를 따로 제공하는 함수가 있는 Proxy Class입니다.

 

사실 여기서부터 기존 코드와 차이가 발생하는데,

Windows에서 제공하는 구현체를 사용하지 않은 탓에 굉장히 긴 초기화 과정을 거치는 경우가 있습니다.

처음에는 배울 때에는 외부 코드를 사용하지 않는 것이 좋다고 생각 했습니다.

하지만 한번 해보니, 아무리 생각해도 이러한 코드들 없이 개발 하는 것은 좀 말이 안된다는 생각이 들었습니다.

그래서 이 부분은 코드 설명이 다 끝난 뒤, 대대적으로 수정을 할 예정입니다.

 

다음에는 DirectX 기본 코드인 D3DApp을 살펴보도록 하겠습니다.

'내용정리 > DirectX12' 카테고리의 다른 글

10. Box Example 3 - BoxApp, WinMain  (0) 2020.09.15
10. Box Example 2 - D3DApp  (0) 2020.09.08
20.08.25 개발일지  (0) 2020.08.25
20.08.21 개발일지  (0) 2020.08.21
20.08.18 개발일지  (0) 2020.08.18

Box Example을 Windows Template랑 합치는데 성공하였습니다.

 

 

 

Register 부분이 주요 틀을 합치는데 유효하고, WinProc가 메뉴 버튼 클릭 이벤트를 담당합니다.

 

다만 현재는 Box Example 코드에 Windows 코드가 포함된 형태라 원래 원하던 기능을 구현한 것은 아닙니다.

 

그래도 이 이상 늘어지는 것보다 일단 진행하는 것이 좋다고 판단하여, 

다음에는 코드 분석을 하고 그 뒤의 챕터를 공부하려 합니다.

 

나중에 시간이 남을 때 DirectX 코드와 Windows 코드를 분리하고 이후 다시 한번 코드를 공유하겠습니다.

 

더보기
// DirectX12ExampleCode.cpp : Defines the entry point for the application.
//

#include "framework.h"
#include "DirectX12ExampleCode.h"
#include "Example/BoxApp.h"

// Forward declarations of functions included in this code module:
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // TODO: Place code here.
    D3DApp* App = new BoxApp(hInstance);

    // Initialize global strings
    //LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    //LoadStringW(hInstance, IDC_DIRECTX12EXAMPLECODE, szWindowClass, MAX_LOADSTRING);

    // Perform application initialization:
    if (!App->Initialize())
    {
        return FALSE;
    }

    // Main message loop:
    try
    {
        return App->run();
    }
    catch (DXException& e)
    {
        MessageBox(nullptr, e.ToString().c_str(), L"HR Failed", MB_OK);
        return 0;
    }
}

 

더보기
#include "D3DApp.h"
#include <cassert>

using namespace std;
using namespace Microsoft::WRL;

LRESULT CALLBACK
MainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	return D3DApp::GetApp()->MsgProc(hwnd, msg, wParam, lParam);
}

D3DApp* D3DApp::mApp = nullptr;
D3DApp* D3DApp::GetApp()
{
	return mApp;
}

D3DApp::D3DApp(HINSTANCE hInstance) : mhAppInst(hInstance)
{
	assert(mApp == nullptr);
	mApp = this;
}

D3DApp::~D3DApp()
{
	if (md3dDevice != nullptr)
	{
		FlushCommandQueue();
	}
}

HINSTANCE D3DApp::AppInst() const
{
	return mhAppInst;
}

HWND D3DApp::MainWnd() const
{
	return mhMainWnd;
}

float D3DApp::AspectRatio() const
{
	return static_cast<float>(mClientWidth) / mClientHeight;
}

bool D3DApp::Get4xMsaaState() const
{
	return m4xMsaaState;
}

void D3DApp::Set4xMsaaState(bool value)
{
	if (m4xMsaaState != value)
	{
		m4xMsaaState = value;
		CreateSwapChain();
		OnResize();
	}
}

int D3DApp::run()
{
	//HACCEL hAccelTable = LoadAccelerators(mhAppInst, MAKEINTRESOURCE(IDC_DIRECTX12EXAMPLECODE));
	MSG msg{ 0 };
	mTimer.Reset();

	while (msg.message != WM_QUIT)
	{
		//TranslateAccelerator(msg.hwnd, hAccelTable, &msg)
		if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else
		{
			mTimer.Tick();

			if (!mAppPaused)
			{
				CalculateFrameStats();
				Update(mTimer);
				Draw(mTimer);
			}
			else
			{
				Sleep(100);
			}
		}
	}
	return static_cast<int>(msg.wParam);
}

bool D3DApp::Initialize()
{
	if (!InitMainWindow())
	{
		return false;
	}
	if (!InitDirect3D())
	{
		return false;
	}

	OnResize();
	return true;
}

void D3DApp::CreateRtvAndDsvDescriptorHeaps()
{
	D3D12_DESCRIPTOR_HEAP_DESC rtvheapDesc;
	rtvheapDesc.NumDescriptors = SwapChainBufferCount;
	rtvheapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
	rtvheapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
	rtvheapDesc.NodeMask = 0;
	ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&rtvheapDesc, IID_PPV_ARGS(mRtvHeap.GetAddressOf())));

	D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc;
	dsvHeapDesc.NumDescriptors = 1;
	dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
	dsvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
	dsvHeapDesc.NodeMask = 0;
	ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&dsvHeapDesc, IID_PPV_ARGS(mDsvHeap.GetAddressOf())));
}

void D3DApp::OnResize()
{
	assert(md3dDevice);
	assert(mSwapChain);
	assert(mDirectCmdListAlloc);

	FlushCommandQueue();

	ThrowIfFailed(mCommandList->Reset(mDirectCmdListAlloc.Get(), nullptr));
	
	for (int i = 0; i < SwapChainBufferCount; ++i)
	{
		mSwapChainbuffer[i].Reset();
	}
	mDepthStencilBuffer.Reset();

	ThrowIfFailed(mSwapChain->ResizeBuffers(
		SwapChainBufferCount, 
		mClientWidth, 
		mClientHeight, 
		mBackBufferFormat, 
		DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH));

	mCurrentBackBuffer = 0;

	D3D12_CPU_DESCRIPTOR_HANDLE rtvHeapHandle(mRtvHeap->GetCPUDescriptorHandleForHeapStart());
	for (UINT i = 0; i < SwapChainBufferCount; ++i)
	{
		ThrowIfFailed(mSwapChain->GetBuffer(i, IID_PPV_ARGS(&mSwapChainbuffer[i])));
		md3dDevice->CreateRenderTargetView(mSwapChainbuffer[i].Get(), nullptr, rtvHeapHandle);
		rtvHeapHandle.ptr += mRtvDescriptorSize;
	}

	D3D12_RESOURCE_DESC depthStencilDesc;
	depthStencilDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
	depthStencilDesc.Alignment = 0;
	depthStencilDesc.Width = mClientWidth;
	depthStencilDesc.Height = mClientHeight;
	depthStencilDesc.DepthOrArraySize = 1;
	depthStencilDesc.MipLevels = 1;
	
	depthStencilDesc.Format = DXGI_FORMAT_R24G8_TYPELESS;
	depthStencilDesc.SampleDesc.Count = m4xMsaaState ? 4 : 1;
	depthStencilDesc.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
	depthStencilDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
	depthStencilDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;

	D3D12_CLEAR_VALUE optClear;
	optClear.Format = mDepthStencilFormat;
	optClear.DepthStencil.Depth = 1.f;
	optClear.DepthStencil.Stencil = 0;
	
	D3D12_HEAP_PROPERTIES HeapProperty;
	HeapProperty.Type = D3D12_HEAP_TYPE_DEFAULT;
	HeapProperty.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
	HeapProperty.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
	HeapProperty.CreationNodeMask = 1;
	HeapProperty.VisibleNodeMask = 1;

	ThrowIfFailed(md3dDevice->CreateCommittedResource(
		&HeapProperty, D3D12_HEAP_FLAG_NONE, 
		&depthStencilDesc, D3D12_RESOURCE_STATE_COMMON, 
		&optClear, IID_PPV_ARGS(mDepthStencilBuffer.GetAddressOf())));

	D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc;
	dsvDesc.Flags = D3D12_DSV_FLAG_NONE;
	dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
	dsvDesc.Format = mDepthStencilFormat;
	dsvDesc.Texture2D.MipSlice = 0;
	md3dDevice->CreateDepthStencilView(mDepthStencilBuffer.Get(), &dsvDesc, DepthStencilView());

	D3D12_RESOURCE_BARRIER Barrier;
	ZeroMemory(&Barrier, sizeof(Barrier));
	Barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
	Barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
	Barrier.Transition.pResource = mDepthStencilBuffer.Get();
	Barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COMMON;
	Barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_DEPTH_WRITE;
	mCommandList->ResourceBarrier(1, &Barrier);

	ThrowIfFailed(mCommandList->Close());
	ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };
	mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);

	FlushCommandQueue();

	mScreenViewport.TopLeftX = 0;
	mScreenViewport.TopLeftY = 0;
	mScreenViewport.Width = static_cast<float>(mClientWidth);
	mScreenViewport.Height = static_cast<float>(mClientHeight);
	mScreenViewport.MinDepth = 0.0f;
	mScreenViewport.MaxDepth = 1.0f;

	mScissorRect = { 0, 0, mClientWidth, mClientHeight };
}

LRESULT D3DApp::MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	//WndProc
	switch (msg)
	{
	case WM_COMMAND:
	{
		int wmId = LOWORD(wParam);
		// Parse the menu selections:
		switch (wmId)
		{
		case IDM_ABOUT:
			//DialogBox(mhAppInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, About);
			break;
		case ID_EXAMPLE_CHAPTER6:

			break;
		case IDM_EXIT:
			DestroyWindow(hwnd);
			break;
		default:
			return DefWindowProc(hwnd, msg, wParam, lParam);
		}
	}
		break;
	case WM_PAINT:
	{
		PAINTSTRUCT ps;
		HDC hdc = BeginPaint(hwnd, &ps);
		// TODO: Add any drawing code that uses hdc here...
		EndPaint(hwnd, &ps);
	}
	break;
	case WM_ACTIVATE:
		if (LOWORD(wParam) == WA_INACTIVE)
		{
			mAppPaused = true;
			mTimer.Stop();
		}
		else
		{
			mAppPaused = false;
			mTimer.Start();
		}
		return 0;
	case WM_SIZE:
		mClientWidth = LOWORD(lParam);
		mClientHeight = HIWORD(lParam);
		if (md3dDevice)
		{
			if (wParam == SIZE_MINIMIZED)
			{
				mAppPaused = true;
				mMinimized = true;
				mMaximized = false;
			}
			else if (wParam == SIZE_MAXIMIZED)
			{
				mAppPaused = false;
				mMinimized = false;
				mMaximized = true;
				OnResize();
			}
			else if (wParam == SIZE_RESTORED)
			{
				if (mMinimized)
				{
					mAppPaused = false;
					mMinimized = false;
					OnResize();
				}
				else if (mMaximized)
				{
					mAppPaused = false;
					mMaximized = false;
					OnResize();
				}
				else if (mResizing)
				{

				}
				else
				{
					OnResize();
				}
			}
		}
		return 0;
	case WM_ENTERSIZEMOVE:
		mAppPaused = true;
		mResizing = true;
		mTimer.Stop();
		return 0;
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_MENUCHAR:
		return MAKELRESULT(0, MNC_CLOSE);
	case WM_GETMINMAXINFO:
		((MINMAXINFO*)lParam)->ptMinTrackSize.x = 200;
		((MINMAXINFO*)lParam)->ptMinTrackSize.y = 200;
		return 0;
	case WM_LBUTTONDOWN:
	case WM_MBUTTONDOWN:
	case WM_RBUTTONDOWN:
		OnMouseDown(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
		return 0;
	case WM_LBUTTONUP:
	case WM_MBUTTONUP:
	case WM_RBUTTONUP:
		OnMouseUp(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
		return 0;
	case WM_MOUSEMOVE:
		OnMouseMove(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
		return 0;
	case WM_KEYUP:
		if (wParam == VK_ESCAPE)
		{
			PostQuitMessage(0);
		}
		else if (static_cast<int>(wParam) == VK_F2)
		{
			Set4xMsaaState(!m4xMsaaState);
		}
		return 0;
	}
	return DefWindowProc(hwnd, msg, wParam, lParam);
}

bool D3DApp::InitMainWindow()
{
	WNDCLASSEXW wc;
	if (!MyRegisterClass(wc))
	{
		MessageBox(0, L"RegisterClass Failed.", 0, 0);
		return false;
	}
	return InitInstance(SW_SHOW);
}

bool D3DApp::InitDirect3D()
{
#if defined(DEBUG) || defined(_DEBUG)
	{
		ComPtr<ID3D12Debug> debugController;
		ThrowIfFailed(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)));
		debugController->EnableDebugLayer();
	}
#endif

	ThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(&mdxgiFactory)));

	HRESULT hardwareResult = D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_12_1, IID_PPV_ARGS(&md3dDevice));

	if (FAILED(hardwareResult))
	{
		ComPtr<IDXGIAdapter4> pWarpAdapter;
		ThrowIfFailed(mdxgiFactory->EnumWarpAdapter(IID_PPV_ARGS(&pWarpAdapter)));

		ThrowIfFailed(D3D12CreateDevice(pWarpAdapter.Get(), D3D_FEATURE_LEVEL_12_1, IID_PPV_ARGS(&md3dDevice)));
	}

	ThrowIfFailed(md3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence)));

	mRtvDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
	mDsvDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
	mCbvSrvUavDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

	D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS msQualityLevels;
	msQualityLevels.Format = mBackBufferFormat;
	msQualityLevels.SampleCount = 4;
	msQualityLevels.Flags = D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE;
	msQualityLevels.NumQualityLevels = 0;
	ThrowIfFailed(md3dDevice->CheckFeatureSupport(D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS, &msQualityLevels, sizeof(msQualityLevels)));

	m4xMsaaQuality = msQualityLevels.NumQualityLevels;
	assert(m4xMsaaQuality > 0 && "Unexpected Msaa quality level.");

#ifdef _DEBUG
	LogAdpaters();
#endif
	
	CreateCommandObjects();
	CreateSwapChain();
	CreateRtvAndDsvDescriptorHeaps();

	return true;
}

void D3DApp::CreateCommandObjects()
{
	D3D12_COMMAND_QUEUE_DESC queueDesc = {};
	queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
	queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
	ThrowIfFailed(md3dDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&mCommandQueue)));

	ThrowIfFailed(md3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(mDirectCmdListAlloc.GetAddressOf())));

	ThrowIfFailed(md3dDevice->CreateCommandList(
		0, 
		D3D12_COMMAND_LIST_TYPE_DIRECT, 
		mDirectCmdListAlloc.Get(), 
		nullptr, 
		IID_PPV_ARGS(mCommandList.GetAddressOf())));

	mCommandList->Close();
}

void D3DApp::CreateSwapChain()
{
	mSwapChain.Reset();
	
	DXGI_SWAP_CHAIN_DESC sd;
	sd.BufferDesc.Width = mClientWidth;
	sd.BufferDesc.Height = mClientHeight;
	sd.BufferDesc.RefreshRate.Numerator = 60;
	sd.BufferDesc.RefreshRate.Denominator = 1;
	sd.BufferDesc.Format = mBackBufferFormat;
	sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
	sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
	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;
	sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
	

	ThrowIfFailed(mdxgiFactory->CreateSwapChain(mCommandQueue.Get(), &sd, mSwapChain.GetAddressOf()));
	//ThrowIfFailed(mdxgiFactory->CreateSwapChainForHwnd(mCommandQueue.Get(), mhMainWnd, &sd1, &swapChainFSDesc, nullptr, &mSwapChain));
	//not recommand to use CreateSwapChain
}

void D3DApp::FlushCommandQueue()
{
	++mCurrentFence;
	
	ThrowIfFailed(mCommandQueue->Signal(mFence.Get(), mCurrentFence));

	if (mFence->GetCompletedValue() < mCurrentFence)
	{
		HANDLE eventHandle = CreateEvent(nullptr, false, false, nullptr);
		//HANDLE eventHandle = CreateEventEx(nullptr, false, false, EVENT_ALL_ACCESS);
		//https://github.com/microsoft/DirectX-Graphics-Samples/issues/15

		ThrowIfFailed(mFence->SetEventOnCompletion(mCurrentFence, eventHandle));

		WaitForSingleObject(eventHandle, INFINITE);
		CloseHandle(eventHandle);
	}
}

ID3D12Resource* D3DApp::CurrentBackBuffer() const
{
	return mSwapChainbuffer[mCurrentBackBuffer].Get();
}

D3D12_CPU_DESCRIPTOR_HANDLE D3DApp::CurrentBackBufferView() const
{
	D3D12_CPU_DESCRIPTOR_HANDLE result(mRtvHeap->GetCPUDescriptorHandleForHeapStart());
	result.ptr += mCurrentBackBuffer * mRtvDescriptorSize;
	return result;
}

D3D12_CPU_DESCRIPTOR_HANDLE D3DApp::DepthStencilView() const
{
	return mDsvHeap->GetCPUDescriptorHandleForHeapStart();
}

void D3DApp::CalculateFrameStats()
{
	static int frameCnt = 0;
	static float timeElapse = 0.f;

	++frameCnt;

	if ((mTimer.TotalTime() - timeElapse) >= 1.f)
	{
		float fps = static_cast<float>(frameCnt);
		float mspf = 1000.f / fps;
		
		wstring fpsStr = to_wstring(fps);
		wstring mspStr = to_wstring(mspf);

		wstring windowText = mMainWndCaption + L",    fps: " + fpsStr + L"ms,    pf: " + mspStr;

		SetWindowText(mhMainWnd, windowText.c_str());
		frameCnt = 0;
		timeElapse += 1.f;
	}
}

void D3DApp::LogAdpaters()
{
	UINT i = 0;
	IDXGIAdapter* adapter = nullptr;
	std::vector<IDXGIAdapter*> adapterList;
	while (mdxgiFactory->EnumAdapters(i, &adapter) != DXGI_ERROR_NOT_FOUND)
	{
		DXGI_ADAPTER_DESC desc;
		adapter->GetDesc(&desc);

		std::wstring text = L"***Adpater: ";
		text += desc.Description;
		text += L"\n";

		OutputDebugString(text.c_str());

		adapterList.push_back(adapter);

		++i;
	}

	for (size_t i = 0; i < adapterList.size(); ++i)
	{
		LogAdpaterOutputs(adapterList[i]);
		if (adapterList[i])
		{
			adapterList[i]->Release();
			adapterList[i] = 0;
		}
	}
}

void D3DApp::LogAdpaterOutputs(IDXGIAdapter* adapter)
{
	UINT i = 0;
	IDXGIOutput* output = nullptr;
	while (adapter->EnumOutputs(i, &output) != DXGI_ERROR_NOT_FOUND)
	{
		DXGI_OUTPUT_DESC desc;
		output->GetDesc(&desc);

		std::wstring text = L"***Output: ";
		text += desc.DeviceName;
		text += L"\n";
		OutputDebugString(text.c_str());

		LogOutputDisplayModes(output, mBackBufferFormat);

		if (output)
		{
			output->Release();
			output = 0;
		}

		++i;
	}
}

void D3DApp::LogOutputDisplayModes(IDXGIOutput* output, DXGI_FORMAT format)
{
	UINT count = 0;
	UINT flags = 0;

	output->GetDisplayModeList(format, flags, &count, nullptr);

	std::vector<DXGI_MODE_DESC> modeList(count);
	output->GetDisplayModeList(format, flags, &count, &modeList[0]);

	for (const auto& x : modeList)
	{
		UINT n = x.RefreshRate.Numerator;
		UINT d = x.RefreshRate.Denominator;
		std::wstring text =
			L"Width = " + to_wstring(x.Width) + L" " +
			L"Height = " + to_wstring(x.Height) + L" " +
			L"Refresh = " + to_wstring(n) + L"/" + to_wstring(d) + L"\n";

		::OutputDebugString(text.c_str());
	}
}

//
//  FUNCTION: MyRegisterClass()
//
//  PURPOSE: Registers the window class.
//
ATOM D3DApp::MyRegisterClass(WNDCLASSEXW& wc)
{
	wc.cbSize = sizeof(WNDCLASSEX);
	wc.style = CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc = MainWndProc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = mhAppInst;
	wc.hIcon = LoadIcon(mhAppInst, MAKEINTRESOURCE(IDI_DIRECTX12EXAMPLECODE));
	wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
	wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wc.lpszMenuName = MAKEINTRESOURCEW(IDC_DIRECTX12EXAMPLECODE);
	wc.lpszClassName = L"MainWnd";
	wc.hIconSm = LoadIcon(wc.hInstance, MAKEINTRESOURCE(IDI_SMALL));
	return RegisterClassExW(&wc);
}

//
//   FUNCTION: InitInstance(HINSTANCE, int)
//
//   PURPOSE: Saves instance handle and creates main window
//
//   COMMENTS:
//
//        In this function, we save the instance handle in a global variable and
//        create and display the main program window.
//
BOOL D3DApp::InitInstance(int nCmdShow)
{
	RECT R = { 0, 0, mClientWidth, mClientHeight };
	AdjustWindowRect(&R, WS_OVERLAPPEDWINDOW, false);
	int width = R.right - R.left;
	int height = R.bottom - R.top;
	mhMainWnd = CreateWindow(L"MainWnd", mMainWndCaption.c_str(),
		WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, width, height, nullptr, nullptr, mhAppInst, nullptr);
	if (!mhMainWnd)
	{
		MessageBox(0, L"CreateWindow Failed.", 0, 0);
		return false;
	}

	ShowWindow(mhMainWnd, nCmdShow);
	UpdateWindow(mhMainWnd);

	return true;
}

INT_PTR CALLBACK D3DApp::About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	UNREFERENCED_PARAMETER(lParam);
	switch (message)
	{
	case WM_INITDIALOG:
		return (INT_PTR)TRUE;

	case WM_COMMAND:
		if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
		{
			EndDialog(hDlg, LOWORD(wParam));
			return (INT_PTR)TRUE;
		}
		break;
	}
	return (INT_PTR)FALSE;
}

'내용정리 > DirectX12' 카테고리의 다른 글

10. Box Example 2 - D3DApp  (0) 2020.09.08
10. Box Example 1  (0) 2020.09.04
20.08.21 개발일지  (0) 2020.08.21
20.08.18 개발일지  (0) 2020.08.18
20.08.14 일지  (0) 2020.08.14

드디어 아기다리고 어기다리던 Box 예제를 제대로 출력을 했습니다.

ExcuteCommandList에서 문제가 있어 이를 호출하는 Draw쪽 함수만 계속 보고 있었는데,
우연히 이 링크에서 해답을 찾을 수 있었습니다.

 

https://stackoverflow.com/questions/38888268/updating-a-vertex-buffer-causes-a-debug-layer-error

 

Updating a vertex buffer causes a debug layer error

I updated my Windows 10 to version 1607 which includes additional D3D debug layer checking. My vertex buffer updating code worked without warnings/errors before the update, but now I'm getting an e...

stackoverflow.com

저와 동일한 문제는 아니나,

해답 부분을 읽다가 d3dUtill 예제 코드로의 링크를 타고 들어가 봤더니 CreateDefaultBuffer 부분이 나왔습니다.

이 부분을 찾아보니 구현이 덜되어 있었습니다.

누락된 부분도 있고, 안 된 부분도 있었던 것입니다.

이 부분을 채우자 정상 작동을 하게 되었습니다.

 

이제 남은건 화면 출력을 예제 프로젝트의 프레임에 나오도록 하고, 버튼 클릭으로 변경 할 수 있게 하는 것입니다.

그리고 코드 리뷰를 진행 하면 됩니다.

원래 계획대로라면 말이죠.

 

하지만 개발을 하면서 느낀 점이 있는데, 순정 개발을 해야 하는가? 입니다.

좀 더 적어보자면 예시 코드들은 MS에서 제공하는 DirectX의 기능들을 확장한 확장 라이브러리들을 사용합니다.

물론 이것들이 어떠한 오버헤드를 야기하는 것은 아니고,
이론적으로 요구되지만 기능이 따로 구현되지 않는 것들을 제공합니다. 

즉, 사용성이 매우 올라간다는 것이죠.

 

처음의 저는 그래도 공부하는 것이기에 구조나 흐름 파악이 가능하도록 이러한 것들을 배제하고 개발 했으나,

실제 개발해보니 사용을 권장해야 할 정도로 너무 불편했습니다.

그래서 프레임을 옮겨놓고 나면 d3dUtill이나 d3dx12 같은 확장 코드를 적용하고자 합니다.

물론 이렇게 된다면 책에 있는 코드를 그대로 타이핑 하는것과 큰 차이가 없을 것입니다.

하지만 그렇기에 비교해서 공부하기에는 더 좋다고 생각합니다.

'내용정리 > DirectX12' 카테고리의 다른 글

10. Box Example 1  (0) 2020.09.04
20.08.25 개발일지  (0) 2020.08.25
20.08.18 개발일지  (0) 2020.08.18
20.08.14 일지  (0) 2020.08.14
20.08.07 개발일지  (0) 2020.08.07

오늘은 크러시가 나는 부분을 고치고,
그 뒤에 새로운 문제를 직면하고 이를 고치다가 뛰쳐나갈것만 같은 정신을 붙잡고 있습니다.

 

우선 크러시 부분.

이 코드를 짜면서 몇몇 Class는 상위버전이 나와서 이를 가져다 썼는데, 그 중 하나가 SwapChain이었습니다.

하지만 이 부분이 문제임은 알겠는데 새로 나온 SwapChain1이 어떻게 하면 정상적으로 사용할 수 있는지
알 수 없어 그냥 SwapChain으로 원상복구했습니다.

 

대부분의 문제들은 오타들로 인한 것이거나, 이처럼 최신 코드를 사용하는 방법을 알지 못해서 발생했습니다.

답답한건 라이브러리 코드 구현체를 보면 대충 어느부분을 주의해야하는지 감이 잡히는데.

DirectX12는 특정 헤더만 열려있고 구현체는 닫혀있어 구현체를 알 수가 없습니다.

 

다음 문제가 이런 종류였습니다.

이제 크러시는 나지 않는데, DXError가 발생하면서 Model Shading이 출력되지 않고 있습니다.

에로 코드는 다음과 같습니다.

 

D3D12 ERROR: ID3D12CommandQueue::ExecuteCommandLists: 
Using ClearDepthStencilView on Command List (0x0A75BC28:'Unnamed ID3D12GraphicsCommandList Object'): 
Resource state (0x0: D3D12_RESOURCE_STATE_[COMMON|PRESENT]) of resource (0x0A6DE398:'Unnamed ID3D12Resource Object') (subresource: 1) 
is invalid for use as a depth buffer.  

Expected State Bits (all): 0x10: D3D12_RESOURCE_STATE_DEPTH_WRITE, 
\Actual State: 0x0: D3D12_RESOURCE_STATE_[COMMON|PRESENT], 
Missing State: 0x10: D3D12_RESOURCE_STATE_DEPTH_WRITE. 
[ EXECUTION ERROR #538: INVALID_SUBRESOURCE_STATE]

즉 CommandLists에 담겨있는 Comaand 중 ClearDepthStencilView를 호출하면서 사용하는 resource의
Resource State의 값이 적절치 못하다는 것입니다.

 

여기서 좀 머리가 복잡해졌습니다. 우선 문제가 발생된다고 예상되는 코드 부분을 발췌했습니다.

void BoxApp::Draw(const GameTimer& gt)
{
	ThrowIfFailed(mDirectCmdListAlloc->Reset());
	ThrowIfFailed(mCommandList->Reset(mDirectCmdListAlloc.Get(), mPSO.Get()));

	// Set CommandList
	mCommandList->RSSetViewports(1, &mScreenViewport);
	mCommandList->RSSetScissorRects(1, &mScissorRect);

	// Set CommandList - not problem
	D3D12_RESOURCE_BARRIER BeforeBarrier;
	ZeroMemory(&BeforeBarrier, sizeof(BeforeBarrier));
	BeforeBarrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
	BeforeBarrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
	BeforeBarrier.Transition.pResource = CurrentBackBuffer();
	BeforeBarrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
	BeforeBarrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
	BeforeBarrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;

	mCommandList->ResourceBarrier(1, &BeforeBarrier);

	mCommandList->ClearRenderTargetView(CurrentBackBufferView(), Colors::LightSteelBlue, 0, nullptr);
	mCommandList->ClearDepthStencilView(DepthStencilView(), D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr);

	// Specify the buffers we are going to render to.
	// Set CommandList
	mCommandList->OMSetRenderTargets(1, &CurrentBackBufferView(), true, &DepthStencilView());

	ID3D12DescriptorHeap* descriptorHeaps[] = { mCbvHeap.Get() };
	mCommandList->SetDescriptorHeaps(_countof(descriptorHeaps), descriptorHeaps);

	// Set CommandList
	mCommandList->SetGraphicsRootSignature(mRootSignature.Get());

	// Set CommandList
	mCommandList->IASetVertexBuffers(0, 1, &mBoxGeo->VertexBufferView());
	mCommandList->IASetIndexBuffer(&mBoxGeo->IndexBufferView());
	mCommandList->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

	// Set CommandList
	mCommandList->SetGraphicsRootDescriptorTable(0, mCbvHeap->GetGPUDescriptorHandleForHeapStart());

	mCommandList->DrawIndexedInstanced(mBoxGeo->DrawArgs["box"].IndexCount, 1, 0, 0, 0);

	// Set CommandList - not problem
	D3D12_RESOURCE_BARRIER AfterBarrier;
	ZeroMemory(&AfterBarrier, sizeof(AfterBarrier));
	AfterBarrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
	AfterBarrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
	AfterBarrier.Transition.pResource = CurrentBackBuffer();
	AfterBarrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
	AfterBarrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
	AfterBarrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
	
	mCommandList->ResourceBarrier(1, &AfterBarrier);
	ThrowIfFailed(mCommandList->Close());

	ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };
	mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);

	ThrowIfFailed(mSwapChain->Present(0, 0));
	mCurrentBackBuffer = (mCurrentBackBuffer + 1) % SwapChainBufferCount;

	FlushCommandQueue();
}

 

1. Resource State를 가져다 쓰는 것은 Resource_Barrier 선언 두번 뿐입니다.
둘의 StateBefore와 StateAfter가 대칭인 것으로 보아 아마 고친다면 둘 다 고쳐야 할것입니다.

2. 로그에 찍힌대로만 하면 Resource_State_Present를 Resource_State_Depth_Write로 고치면 될 것입니다. 
하지만 그렇게 되면 그 사이 함수들 부분에서 에러가 발생합니다.

3. 로그에 찍힌 또 다른 요소인 ClearDepthStencilView 부분을 직접 수정해야 하나
원래 코드와 다른 부분이 없어 무엇이 문제인지 파악할 수 없습니다.

 

이 상태에서 Resource_Barrier 사이에 선언된 모든 함수나 파라미터를 건드려 보았으나
유의미한 성과를 얻지 못했습니다.

 

 

정말 참... 정신 나갈 것 같습니다.

거의 문제 하나를 일주일동안 붙들고 있으니 진행도 안되고 미쳐버릴 것 같습니다.

빨리 빌드를 완성했으면 좋겠습니다.

그래야 코드 하나하나 뜯어서 구조를 머릿속에 박아넣을텐데...

'내용정리 > DirectX12' 카테고리의 다른 글

20.08.25 개발일지  (0) 2020.08.25
20.08.21 개발일지  (0) 2020.08.21
20.08.14 일지  (0) 2020.08.14
20.08.07 개발일지  (0) 2020.08.07
20.08.04 일지  (0) 2020.08.04

기존에 error가 발생해 crash를 야기하던 부분들을 오늘에서야 수정할 수 있었습니다.

여러 키워드로 검색을 하고 수정을 해보았으나, 유의미한 성과는 얻지 못하여 

프로그램의 시작서부터 crash 부분까지 모든 코드를 원본 코드랑 비교를 해보았습니다.

그 결과 오타로 인한 잘못된 변수 기입, for문 처리 미숙으로 인한 것임을 발견하였습니다.

고친 직후에는 답을 찾아서 매우 기뻤지만 지금 생각해보면 부끄럽기 짝이 없는 실수입니다.

 

지금은 실행해본 결과 runtime상에서 문제가 발생하고 있습니다.

Draw에서 Command Allocator 관련 함수에서 error가 발생하는데,

여기서부터는 디버깅으로 찾기 어려운 부분이라 역시나 삽질에 가까운 탐색을 진행 중입니다.

 

다음에는 다시 한번 코드단위로 오탈자로 인해 문제가 발생했는지 찾아보고,

그렇지 않다면 기존 코드의 Window 창 부분이 변경 되면서 문제가 발생했는지 확인해볼 생각입니다.

'내용정리 > DirectX12' 카테고리의 다른 글

20.08.21 개발일지  (0) 2020.08.21
20.08.18 개발일지  (0) 2020.08.18
20.08.07 개발일지  (0) 2020.08.07
20.08.04 일지  (0) 2020.08.04
20.07.31 개발일지  (0) 2020.07.31

Compile Shader 부분의 문제는 계속해서 찾아보고 비교하다가 결국 프로젝트를 새로 만드는 지경까지 도달했는데

결국 문제는 오탈자였습니다.

 

파일 이름을 잘못 작성해서 검색이 안되고 있었습니다.

 

그 다음 문제는 PSO 선언 부분이었습니다.

일단 이 부분에서 크래시가 나서 멈춘 상태입니다.

 

그리고 이전에 손을 봤던 BuildConstantBuffers에서 크래시는 안나지만 에러가 발생하는 것을 확인했습니다.

이 부분은 조금 손을 보았는데 해결법을 찾지 못해 StackOverflow에 질문을 올려놓은 상태입니다.

https://stackoverflow.com/questions/63298060/exceed-end-of-the-virtual-address-at-createconstantbufferview

 

Exceed end of the virtual address at CreateConstantBufferView

I'm following "Game Programming using DirectX12" ch.6 codes. But in at ID3DDevice::CreateConstantBufferView, I found D3D12 Error. D3D12 ERROR: ID3D12Device::CreateConstantBufferView: pDe...

stackoverflow.com

혹시라도 이 글을 접한 실력 있는 개발자분들 중 이 문제의 해결법을 알고 계신 분은 공유해주시면 감사하겠습니다.

'내용정리 > DirectX12' 카테고리의 다른 글

20.08.18 개발일지  (0) 2020.08.18
20.08.14 일지  (0) 2020.08.14
20.08.04 일지  (0) 2020.08.04
20.07.31 개발일지  (0) 2020.07.31
20.07.28 개발일지  (0) 2020.07.28

CreateChainSwap 부분이 완전히 수정된 줄 알았는데 변수 값이 미묘하게 맞지 않았습니다.

이 부분을 수정했더니 에러는 해결되었습니다.

 

하지만 다른 부분에서 연달아 에러가 발생하고 있습니다.

 

한 2개를 더 수정했고, 지금은 Compile Shader 부분에서 경로를 탐색하지 못하고 있습니다.

 

한 이틀 정도 밤샘을 했더니 반동이 오고 있어서 많이 하지 못했습니다.

 

그래도 조금이라도 해결이 되어가는 것 같아 위안이 조금 됩니다.

'내용정리 > DirectX12' 카테고리의 다른 글

20.08.14 일지  (0) 2020.08.14
20.08.07 개발일지  (0) 2020.08.07
20.07.31 개발일지  (0) 2020.07.31
20.07.28 개발일지  (0) 2020.07.28
20.07.25 개발일지  (0) 2020.07.25

+ Recent posts