#pragma once

#include "..\Common\DeviceResources.h"
#include "ShaderStructures.h"
#include "..\Common\StepTimer.h"

namespace ExampleCreation
{
	// This sample renderer instantiates a basic rendering pipeline.
	class Sample3DSceneRenderer
	{
	public:
		Sample3DSceneRenderer(const std::shared_ptr<DX::DeviceResources>& deviceResources);
		~Sample3DSceneRenderer();
		void CreateDeviceDependentResources();
		void CreateWindowSizeDependentResources();
		void Update(DX::StepTimer const& timer);
		bool Render();
		void SaveState();

		void StartTracking();
		void TrackingUpdate(float positionX);
		void StopTracking();
		bool IsTracking() { return m_tracking; }

	private:
		void LoadState();
		void Rotate(float radians);

	private:
		// Constant buffers must be 256-byte aligned.
		static const UINT c_alignedConstantBufferSize = (sizeof(ModelViewProjectionConstantBuffer) + 255) & ~255;

		// Cached pointer to device resources.
		std::shared_ptr<DX::DeviceResources> m_deviceResources;

		// Direct3D resources for cube geometry.
		Microsoft::WRL::ComPtr<ID3D12GraphicsCommandList>	m_commandList;
		Microsoft::WRL::ComPtr<ID3D12RootSignature>		m_rootSignature;
		Microsoft::WRL::ComPtr<ID3D12PipelineState>		m_pipelineState;
		Microsoft::WRL::ComPtr<ID3D12DescriptorHeap>		m_cbvHeap;
		Microsoft::WRL::ComPtr<ID3D12Resource>			m_vertexBuffer;
		Microsoft::WRL::ComPtr<ID3D12Resource>			m_indexBuffer;
		Microsoft::WRL::ComPtr<ID3D12Resource>			m_constantBuffer;
		ModelViewProjectionConstantBuffer			m_constantBufferData;
		UINT8*							m_mappedConstantBuffer;
		UINT							m_cbvDescriptorSize;
		D3D12_RECT						m_scissorRect;
		std::vector<byte>					m_vertexShader;
		std::vector<byte>					m_pixelShader;
		D3D12_VERTEX_BUFFER_VIEW				m_vertexBufferView;
		D3D12_INDEX_BUFFER_VIEW					m_indexBufferView;

		// Variables used with the rendering loop.
		bool	m_loadingComplete;
		float	m_radiansPerSecond;
		float	m_angle;
		bool	m_tracking;
	};
}

이전과 마찬가지로 함수에 대해서는 Sample3DSceneRenderer.cpp에서 더 자세히 알아보고, 

우선은 Field 위주로 살펴보자.

 

c_alignedConstantBufferSize는 Constant buffer의 최대 사이즈로, 256으로 맞춰준다.

m_deviceREsources는 DeviceResources의 pointer이다.

 

그 아랫부분은 Template에서 구현하는 Cube에 필요한 것들과, 회전시키는데 필요한 변수들이다.

우선은 넘어가도록 하자.

 

이제 본격적으로 Saple3DSceneRenderer.cpp를 살펴보는데, header 선언에서부터 낯선 header가 눈에 띈다.

#include "pch.h"
#include "Sample3DSceneRenderer.h"

#include "..\Common\DirectXHelper.h"
#include <ppltasks.h>
#include <synchapi.h>

ppltasks.h와 synchapi.h는 사용해본 적이 없을 것이다.

ppltasks.h는 C++ 표준으로 제공하는 병렬 패턴 라이브러리이고,

synchapi.h는 Windows에서 제공하는 System Service 함수들을 포함한다.

https://docs.microsoft.com/ko-kr/cpp/parallel/concrt/parallel-patterns-library-ppl?view=vs-2019

 

PPL(병렬 패턴 라이브러리)

PPL(병렬 패턴 라이브러리)Parallel Patterns Library (PPL) 이 문서의 내용 --> PPL(병렬 패턴 라이브러리)은 여러 애플리케이션을 동시에 개발할 수 있도록 편의성과 확장성을 높이는 명령적 프로그래밍 ��

docs.microsoft.com

https://docs.microsoft.com/en-us/windows/win32/api/synchapi/

 

Synchapi.h header - Win32 apps

01/11/2019 4 minutes to read In this article --> This header is used by System Services. For more information, see: System Services synchapi.h contains the following programming interfaces: Functions Title Description AcquireSRWLockExclusive Acquires a sli

docs.microsoft.com

 

그 다음 보이는 것은 Platform::String의 reference 형으로 선언 된 전역 변수이다.

// Indices into the application state map.
Platform::String^ AngleKey = "Angle";
Platform::String^ TrackingKey = "Tracking";

application state map의 Index들이라고 설명이 되어 있는데, 뒤에 사용처가 나온다면 좀 더 알아보자.

 

// Loads vertex and pixel shaders from files and instantiates the cube geometry.
Sample3DSceneRenderer::Sample3DSceneRenderer(const std::shared_ptr<DX::DeviceResources>& deviceResources) :
	m_loadingComplete(false),
	m_radiansPerSecond(XM_PIDIV4),	// rotate 45 degrees per second
	m_angle(0),
	m_tracking(false),
	m_mappedConstantBuffer(nullptr),
	m_deviceResources(deviceResources)
{
	LoadState();
	ZeroMemory(&m_constantBufferData, sizeof(m_constantBufferData));

	CreateDeviceDependentResources();
	CreateWindowSizeDependentResources();
}

생성자이다. 반드시 DeviceResources를 입력 받아야 한다.

생성자에서는 Rendering loop에 관한 변수들이 초기화 되고 있다.

매 프레임마다 정보가 갱신 되었는지 확인해주는 m_loadingComplete는 아직 생성자 단계에서는 false로.

회전 속도는 π/4(rad). 초기 회전각은 0도. tracking을 확인하는 m_tracking도 생성자 단계에서는 false이다.

 

유일하게 Cube 회전에 관련되지 않은 것은 m_mappedConstantBuffer이다. 

이에 대해서는 뒤에 자세한 설명을 하겠다.

 

생성자 내부에서는 LoadState()를 호출하고, m_constantBufferData를 0으로 초기화 한다.

// Restores the previous state of the renderer.
void Sample3DSceneRenderer::LoadState()
{
	auto state = ApplicationData::Current->LocalSettings->Values;
	if (state->HasKey(AngleKey))
	{
		m_angle = safe_cast<IPropertyValue^>(state->Lookup(AngleKey))->GetSingle();
		state->Remove(AngleKey);
	}
	if (state->HasKey(TrackingKey))
	{
		m_tracking = safe_cast<IPropertyValue^>(state->Lookup(TrackingKey))->GetBoolean();
		state->Remove(TrackingKey);
	}
}

LoadState는 이전 renderer 상태를 복구 시키는 함수이다. 

이전 상태에서의 localSettings 값을 받아와, 이 값이 AngleKey일 경우에 m_angle 값을 저장하고,

이전 값이 TrackingKey일 경우에는 m_tracking 상태를 저장한다.

 

이 뒤에 CreateDeviceDependentResources()를 호출한다.

void Sample3DSceneRenderer::CreateDeviceDependentResources()
{
	auto d3dDevice = m_deviceResources->GetD3DDevice();

	// Create a root signature with a single constant buffer slot.
	{
		CD3DX12_DESCRIPTOR_RANGE range;
		CD3DX12_ROOT_PARAMETER parameter;

		range.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0);
		parameter.InitAsDescriptorTable(1, &range, D3D12_SHADER_VISIBILITY_VERTEX);

		D3D12_ROOT_SIGNATURE_FLAGS rootSignatureFlags =
			D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT | // Only the input assembler stage needs access to the constant buffer.
			D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS |
			D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS |
			D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS |
			D3D12_ROOT_SIGNATURE_FLAG_DENY_PIXEL_SHADER_ROOT_ACCESS;

		CD3DX12_ROOT_SIGNATURE_DESC descRootSignature;
		descRootSignature.Init(1, &parameter, 0, nullptr, rootSignatureFlags);

		ComPtr<ID3DBlob> pSignature;
		ComPtr<ID3DBlob> pError;
		DX::ThrowIfFailed(D3D12SerializeRootSignature(&descRootSignature, D3D_ROOT_SIGNATURE_VERSION_1, pSignature.GetAddressOf(), pError.GetAddressOf()));
		DX::ThrowIfFailed(d3dDevice->CreateRootSignature(0, pSignature->GetBufferPointer(), pSignature->GetBufferSize(), IID_PPV_ARGS(&m_rootSignature)));
        NAME_D3D12_OBJECT(m_rootSignature);
	}

	// Load shaders asynchronously.
	auto createVSTask = DX::ReadDataAsync(L"SampleVertexShader.cso").then([this](std::vector<byte>& fileData) {
		m_vertexShader = fileData;
	});

	auto createPSTask = DX::ReadDataAsync(L"SamplePixelShader.cso").then([this](std::vector<byte>& fileData) {
		m_pixelShader = fileData;
	});

	// Create the pipeline state once the shaders are loaded.
	auto createPipelineStateTask = (createPSTask && createVSTask).then([this]() {

		static const D3D12_INPUT_ELEMENT_DESC inputLayout[] =
		{
			{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
			{ "COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
		};

		D3D12_GRAPHICS_PIPELINE_STATE_DESC state = {};
		state.InputLayout = { inputLayout, _countof(inputLayout) };
		state.pRootSignature = m_rootSignature.Get();
        state.VS = CD3DX12_SHADER_BYTECODE(&m_vertexShader[0], m_vertexShader.size());
        state.PS = CD3DX12_SHADER_BYTECODE(&m_pixelShader[0], m_pixelShader.size());
		state.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
		state.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
		state.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);
		state.SampleMask = UINT_MAX;
		state.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
		state.NumRenderTargets = 1;
		state.RTVFormats[0] = m_deviceResources->GetBackBufferFormat();
		state.DSVFormat = m_deviceResources->GetDepthBufferFormat();
		state.SampleDesc.Count = 1;

		DX::ThrowIfFailed(m_deviceResources->GetD3DDevice()->CreateGraphicsPipelineState(&state, IID_PPV_ARGS(&m_pipelineState)));

		// Shader data can be deleted once the pipeline state is created.
		m_vertexShader.clear();
		m_pixelShader.clear();
	});

	// Create and upload cube geometry resources to the GPU.
	auto createAssetsTask = createPipelineStateTask.then([this]() {
		auto d3dDevice = m_deviceResources->GetD3DDevice();

		// Create a command list.
		DX::ThrowIfFailed(d3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_deviceResources->GetCommandAllocator(), m_pipelineState.Get(), IID_PPV_ARGS(&m_commandList)));
        NAME_D3D12_OBJECT(m_commandList);

		// Cube vertices. Each vertex has a position and a color.
		VertexPositionColor cubeVertices[] =
		{
			{ XMFLOAT3(-0.5f, -0.5f, -0.5f), XMFLOAT3(0.0f, 0.0f, 0.0f) },
			{ XMFLOAT3(-0.5f, -0.5f,  0.5f), XMFLOAT3(0.0f, 0.0f, 1.0f) },
			{ XMFLOAT3(-0.5f,  0.5f, -0.5f), XMFLOAT3(0.0f, 1.0f, 0.0f) },
			{ XMFLOAT3(-0.5f,  0.5f,  0.5f), XMFLOAT3(0.0f, 1.0f, 1.0f) },
			{ XMFLOAT3(0.5f, -0.5f, -0.5f), XMFLOAT3(1.0f, 0.0f, 0.0f) },
			{ XMFLOAT3(0.5f, -0.5f,  0.5f), XMFLOAT3(1.0f, 0.0f, 1.0f) },
			{ XMFLOAT3(0.5f,  0.5f, -0.5f), XMFLOAT3(1.0f, 1.0f, 0.0f) },
			{ XMFLOAT3(0.5f,  0.5f,  0.5f), XMFLOAT3(1.0f, 1.0f, 1.0f) },
		};

		const UINT vertexBufferSize = sizeof(cubeVertices);

		// Create the vertex buffer resource in the GPU's default heap and copy vertex data into it using the upload heap.
		// The upload resource must not be released until after the GPU has finished using it.
		Microsoft::WRL::ComPtr<ID3D12Resource> vertexBufferUpload;

		CD3DX12_HEAP_PROPERTIES defaultHeapProperties(D3D12_HEAP_TYPE_DEFAULT);
		CD3DX12_RESOURCE_DESC vertexBufferDesc = CD3DX12_RESOURCE_DESC::Buffer(vertexBufferSize);
		DX::ThrowIfFailed(d3dDevice->CreateCommittedResource(
			&defaultHeapProperties,
			D3D12_HEAP_FLAG_NONE,
			&vertexBufferDesc,
			D3D12_RESOURCE_STATE_COPY_DEST,
			nullptr,
			IID_PPV_ARGS(&m_vertexBuffer)));

		CD3DX12_HEAP_PROPERTIES uploadHeapProperties(D3D12_HEAP_TYPE_UPLOAD);
		DX::ThrowIfFailed(d3dDevice->CreateCommittedResource(
			&uploadHeapProperties,
			D3D12_HEAP_FLAG_NONE,
			&vertexBufferDesc,
			D3D12_RESOURCE_STATE_GENERIC_READ,
			nullptr,
			IID_PPV_ARGS(&vertexBufferUpload)));

        NAME_D3D12_OBJECT(m_vertexBuffer);

		// Upload the vertex buffer to the GPU.
		{
			D3D12_SUBRESOURCE_DATA vertexData = {};
			vertexData.pData = reinterpret_cast<BYTE*>(cubeVertices);
			vertexData.RowPitch = vertexBufferSize;
			vertexData.SlicePitch = vertexData.RowPitch;

			UpdateSubresources(m_commandList.Get(), m_vertexBuffer.Get(), vertexBufferUpload.Get(), 0, 0, 1, &vertexData);

			CD3DX12_RESOURCE_BARRIER vertexBufferResourceBarrier =
				CD3DX12_RESOURCE_BARRIER::Transition(m_vertexBuffer.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER);
			m_commandList->ResourceBarrier(1, &vertexBufferResourceBarrier);
		}

		// Load mesh indices. Each trio of indices represents a triangle to be rendered on the screen.
		// For example: 0,2,1 means that the vertices with indexes 0, 2 and 1 from the vertex buffer compose the
		// first triangle of this mesh.
		unsigned short cubeIndices[] =
		{
			0, 2, 1, // -x
			1, 2, 3,

			4, 5, 6, // +x
			5, 7, 6,

			0, 1, 5, // -y
			0, 5, 4,

			2, 6, 7, // +y
			2, 7, 3,

			0, 4, 6, // -z
			0, 6, 2,

			1, 3, 7, // +z
			1, 7, 5,
		};

		const UINT indexBufferSize = sizeof(cubeIndices);

		// Create the index buffer resource in the GPU's default heap and copy index data into it using the upload heap.
		// The upload resource must not be released until after the GPU has finished using it.
		Microsoft::WRL::ComPtr<ID3D12Resource> indexBufferUpload;

		CD3DX12_RESOURCE_DESC indexBufferDesc = CD3DX12_RESOURCE_DESC::Buffer(indexBufferSize);
		DX::ThrowIfFailed(d3dDevice->CreateCommittedResource(
			&defaultHeapProperties,
			D3D12_HEAP_FLAG_NONE,
			&indexBufferDesc,
			D3D12_RESOURCE_STATE_COPY_DEST,
			nullptr,
			IID_PPV_ARGS(&m_indexBuffer)));

		DX::ThrowIfFailed(d3dDevice->CreateCommittedResource(
			&uploadHeapProperties,
			D3D12_HEAP_FLAG_NONE,
			&indexBufferDesc,
			D3D12_RESOURCE_STATE_GENERIC_READ,
			nullptr,
			IID_PPV_ARGS(&indexBufferUpload)));

		NAME_D3D12_OBJECT(m_indexBuffer);

		// Upload the index buffer to the GPU.
		{
			D3D12_SUBRESOURCE_DATA indexData = {};
			indexData.pData = reinterpret_cast<BYTE*>(cubeIndices);
			indexData.RowPitch = indexBufferSize;
			indexData.SlicePitch = indexData.RowPitch;

			UpdateSubresources(m_commandList.Get(), m_indexBuffer.Get(), indexBufferUpload.Get(), 0, 0, 1, &indexData);

			CD3DX12_RESOURCE_BARRIER indexBufferResourceBarrier =
				CD3DX12_RESOURCE_BARRIER::Transition(m_indexBuffer.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_INDEX_BUFFER);
			m_commandList->ResourceBarrier(1, &indexBufferResourceBarrier);
		}

		// Create a descriptor heap for the constant buffers.
		{
			D3D12_DESCRIPTOR_HEAP_DESC heapDesc = {};
			heapDesc.NumDescriptors = DX::c_frameCount;
			heapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
			// This flag indicates that this descriptor heap can be bound to the pipeline and that descriptors contained in it can be referenced by a root table.
			heapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
			DX::ThrowIfFailed(d3dDevice->CreateDescriptorHeap(&heapDesc, IID_PPV_ARGS(&m_cbvHeap)));

            NAME_D3D12_OBJECT(m_cbvHeap);
		}

		CD3DX12_RESOURCE_DESC constantBufferDesc = CD3DX12_RESOURCE_DESC::Buffer(DX::c_frameCount * c_alignedConstantBufferSize);
		DX::ThrowIfFailed(d3dDevice->CreateCommittedResource(
			&uploadHeapProperties,
			D3D12_HEAP_FLAG_NONE,
			&constantBufferDesc,
			D3D12_RESOURCE_STATE_GENERIC_READ,
			nullptr,
			IID_PPV_ARGS(&m_constantBuffer)));

        NAME_D3D12_OBJECT(m_constantBuffer);

		// Create constant buffer views to access the upload buffer.
		D3D12_GPU_VIRTUAL_ADDRESS cbvGpuAddress = m_constantBuffer->GetGPUVirtualAddress();
		CD3DX12_CPU_DESCRIPTOR_HANDLE cbvCpuHandle(m_cbvHeap->GetCPUDescriptorHandleForHeapStart());
		m_cbvDescriptorSize = d3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

		for (int n = 0; n < DX::c_frameCount; n++)
		{
			D3D12_CONSTANT_BUFFER_VIEW_DESC desc;
			desc.BufferLocation = cbvGpuAddress;
			desc.SizeInBytes = c_alignedConstantBufferSize;
			d3dDevice->CreateConstantBufferView(&desc, cbvCpuHandle);

			cbvGpuAddress += desc.SizeInBytes;
			cbvCpuHandle.Offset(m_cbvDescriptorSize);
		}

		// Map the constant buffers.
		CD3DX12_RANGE readRange(0, 0);		// We do not intend to read from this resource on the CPU.
		DX::ThrowIfFailed(m_constantBuffer->Map(0, &readRange, reinterpret_cast<void**>(&m_mappedConstantBuffer)));
		ZeroMemory(m_mappedConstantBuffer, DX::c_frameCount * c_alignedConstantBufferSize);
		// We don't unmap this until the app closes. Keeping things mapped for the lifetime of the resource is okay.

		// Close the command list and execute it to begin the vertex/index buffer copy into the GPU's default heap.
		DX::ThrowIfFailed(m_commandList->Close());
		ID3D12CommandList* ppCommandLists[] = { m_commandList.Get() };
		m_deviceResources->GetCommandQueue()->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);

		// Create vertex/index buffer views.
		m_vertexBufferView.BufferLocation = m_vertexBuffer->GetGPUVirtualAddress();
		m_vertexBufferView.StrideInBytes = sizeof(VertexPositionColor);
		m_vertexBufferView.SizeInBytes = sizeof(cubeVertices);

		m_indexBufferView.BufferLocation = m_indexBuffer->GetGPUVirtualAddress();
		m_indexBufferView.SizeInBytes = sizeof(cubeIndices);
		m_indexBufferView.Format = DXGI_FORMAT_R16_UINT;

		// Wait for the command list to finish executing; the vertex/index buffers need to be uploaded to the GPU before the upload resources go out of scope.
		m_deviceResources->WaitForGpu();
	});

	createAssetsTask.then([this]() {
		m_loadingComplete = true;
	});
}

상당히 긴 코드인데, 하나하나 살펴보자.

먼저 D3DDevice 객체를 받아오고, single constant buffer slot에 root signature를 생성한다.

CD3DX12_DESCRIPTOR_RANGE range;
CD3DX12_ROOT_PARAMETER parameter;

range.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0);
parameter.InitAsDescriptorTable(1, &range, D3D12_SHADER_VISIBILITY_VERTEX);

D3D12_ROOT_SIGNATURE_FLAGS rootSignatureFlags =
	D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT | // Only the input assembler stage needs access to the constant buffer.
	D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS |
	D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS |
	D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS |
	D3D12_ROOT_SIGNATURE_FLAG_DENY_PIXEL_SHADER_ROOT_ACCESS;

CD3DX12_ROOT_SIGNATURE_DESC descRootSignature;
descRootSignature.Init(1, &parameter, 0, nullptr, rootSignatureFlags);

ComPtr<ID3DBlob> pSignature;
ComPtr<ID3DBlob> pError;
DX::ThrowIfFailed(D3D12SerializeRootSignature(&descRootSignature, D3D_ROOT_SIGNATURE_VERSION_1, pSignature.GetAddressOf(), pError.GetAddressOf()));
DX::ThrowIfFailed(d3dDevice->CreateRootSignature(0, pSignature->GetBufferPointer(), pSignature->GetBufferSize(), IID_PPV_ARGS(&m_rootSignature)));
NAME_D3D12_OBJECT(m_rootSignature);

 

그리고 나서 shander를 비동기적으로 불러온다.

여기서 중요한 점은 비동기적이라는 점이다.

// Load shaders asynchronously.
auto createVSTask = DX::ReadDataAsync(L"SampleVertexShader.cso").then([this](std::vector<byte>& fileData) {
	m_vertexShader = fileData;
});

auto createPSTask = DX::ReadDataAsync(L"SamplePixelShader.cso").then([this](std::vector<byte>& fileData) {
	m_pixelShader = fileData;
});

 

이렇게 shader들이 불러오게 된다면, pipeline state를 생성한다.

// Create the pipeline state once the shaders are loaded.
auto createPipelineStateTask = (createPSTask && createVSTask).then([this]() {

	static const D3D12_INPUT_ELEMENT_DESC inputLayout[] =
	{
		{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
		{ "COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
	};

	D3D12_GRAPHICS_PIPELINE_STATE_DESC state = {};
	state.InputLayout = { inputLayout, _countof(inputLayout) };
	state.pRootSignature = m_rootSignature.Get();
    state.VS = CD3DX12_SHADER_BYTECODE(&m_vertexShader[0], m_vertexShader.size());
    state.PS = CD3DX12_SHADER_BYTECODE(&m_pixelShader[0], m_pixelShader.size());
	state.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
	state.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
	state.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);
	state.SampleMask = UINT_MAX;
	state.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
	state.NumRenderTargets = 1;
	state.RTVFormats[0] = m_deviceResources->GetBackBufferFormat();
	state.DSVFormat = m_deviceResources->GetDepthBufferFormat();
	state.SampleDesc.Count = 1;

	DX::ThrowIfFailed(m_deviceResources->GetD3DDevice()->CreateGraphicsPipelineState(&state, IID_PPV_ARGS(&m_pipelineState)));

	// Shader data can be deleted once the pipeline state is created.
	m_vertexShader.clear();
	m_pixelShader.clear();
});

 

이렇게 생성된 Pipeline에는 Cube Geometry Resources를 생성하고, GPU로 불러오는 작업이 차례대로 들어간다.

auto createAssetsTask = createPipelineStateTask.then([this]() {
		auto d3dDevice = m_deviceResources->GetD3DDevice();

		// Create a command list.
		DX::ThrowIfFailed(d3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_deviceResources->GetCommandAllocator(), m_pipelineState.Get(), IID_PPV_ARGS(&m_commandList)));
        NAME_D3D12_OBJECT(m_commandList);

		// Cube vertices. Each vertex has a position and a color.
		VertexPositionColor cubeVertices[] =
		{
			{ XMFLOAT3(-0.5f, -0.5f, -0.5f), XMFLOAT3(0.0f, 0.0f, 0.0f) },
			{ XMFLOAT3(-0.5f, -0.5f,  0.5f), XMFLOAT3(0.0f, 0.0f, 1.0f) },
			{ XMFLOAT3(-0.5f,  0.5f, -0.5f), XMFLOAT3(0.0f, 1.0f, 0.0f) },
			{ XMFLOAT3(-0.5f,  0.5f,  0.5f), XMFLOAT3(0.0f, 1.0f, 1.0f) },
			{ XMFLOAT3(0.5f, -0.5f, -0.5f), XMFLOAT3(1.0f, 0.0f, 0.0f) },
			{ XMFLOAT3(0.5f, -0.5f,  0.5f), XMFLOAT3(1.0f, 0.0f, 1.0f) },
			{ XMFLOAT3(0.5f,  0.5f, -0.5f), XMFLOAT3(1.0f, 1.0f, 0.0f) },
			{ XMFLOAT3(0.5f,  0.5f,  0.5f), XMFLOAT3(1.0f, 1.0f, 1.0f) },
		};

		const UINT vertexBufferSize = sizeof(cubeVertices);

		// Create the vertex buffer resource in the GPU's default heap and copy vertex data into it using the upload heap.
		// The upload resource must not be released until after the GPU has finished using it.
		Microsoft::WRL::ComPtr<ID3D12Resource> vertexBufferUpload;

		CD3DX12_HEAP_PROPERTIES defaultHeapProperties(D3D12_HEAP_TYPE_DEFAULT);
		CD3DX12_RESOURCE_DESC vertexBufferDesc = CD3DX12_RESOURCE_DESC::Buffer(vertexBufferSize);
		DX::ThrowIfFailed(d3dDevice->CreateCommittedResource(
			&defaultHeapProperties,
			D3D12_HEAP_FLAG_NONE,
			&vertexBufferDesc,
			D3D12_RESOURCE_STATE_COPY_DEST,
			nullptr,
			IID_PPV_ARGS(&m_vertexBuffer)));

		CD3DX12_HEAP_PROPERTIES uploadHeapProperties(D3D12_HEAP_TYPE_UPLOAD);
		DX::ThrowIfFailed(d3dDevice->CreateCommittedResource(
			&uploadHeapProperties,
			D3D12_HEAP_FLAG_NONE,
			&vertexBufferDesc,
			D3D12_RESOURCE_STATE_GENERIC_READ,
			nullptr,
			IID_PPV_ARGS(&vertexBufferUpload)));

        NAME_D3D12_OBJECT(m_vertexBuffer);

		// Upload the vertex buffer to the GPU.
		{
			D3D12_SUBRESOURCE_DATA vertexData = {};
			vertexData.pData = reinterpret_cast<BYTE*>(cubeVertices);
			vertexData.RowPitch = vertexBufferSize;
			vertexData.SlicePitch = vertexData.RowPitch;

			UpdateSubresources(m_commandList.Get(), m_vertexBuffer.Get(), vertexBufferUpload.Get(), 0, 0, 1, &vertexData);

			CD3DX12_RESOURCE_BARRIER vertexBufferResourceBarrier =
				CD3DX12_RESOURCE_BARRIER::Transition(m_vertexBuffer.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER);
			m_commandList->ResourceBarrier(1, &vertexBufferResourceBarrier);
		}

		// Load mesh indices. Each trio of indices represents a triangle to be rendered on the screen.
		// For example: 0,2,1 means that the vertices with indexes 0, 2 and 1 from the vertex buffer compose the
		// first triangle of this mesh.
		unsigned short cubeIndices[] =
		{
			0, 2, 1, // -x
			1, 2, 3,

			4, 5, 6, // +x
			5, 7, 6,

			0, 1, 5, // -y
			0, 5, 4,

			2, 6, 7, // +y
			2, 7, 3,

			0, 4, 6, // -z
			0, 6, 2,

			1, 3, 7, // +z
			1, 7, 5,
		};

		const UINT indexBufferSize = sizeof(cubeIndices);

		// Create the index buffer resource in the GPU's default heap and copy index data into it using the upload heap.
		// The upload resource must not be released until after the GPU has finished using it.
		Microsoft::WRL::ComPtr<ID3D12Resource> indexBufferUpload;

		CD3DX12_RESOURCE_DESC indexBufferDesc = CD3DX12_RESOURCE_DESC::Buffer(indexBufferSize);
		DX::ThrowIfFailed(d3dDevice->CreateCommittedResource(
			&defaultHeapProperties,
			D3D12_HEAP_FLAG_NONE,
			&indexBufferDesc,
			D3D12_RESOURCE_STATE_COPY_DEST,
			nullptr,
			IID_PPV_ARGS(&m_indexBuffer)));

		DX::ThrowIfFailed(d3dDevice->CreateCommittedResource(
			&uploadHeapProperties,
			D3D12_HEAP_FLAG_NONE,
			&indexBufferDesc,
			D3D12_RESOURCE_STATE_GENERIC_READ,
			nullptr,
			IID_PPV_ARGS(&indexBufferUpload)));

		NAME_D3D12_OBJECT(m_indexBuffer);

		// Upload the index buffer to the GPU.
		{
			D3D12_SUBRESOURCE_DATA indexData = {};
			indexData.pData = reinterpret_cast<BYTE*>(cubeIndices);
			indexData.RowPitch = indexBufferSize;
			indexData.SlicePitch = indexData.RowPitch;

			UpdateSubresources(m_commandList.Get(), m_indexBuffer.Get(), indexBufferUpload.Get(), 0, 0, 1, &indexData);

			CD3DX12_RESOURCE_BARRIER indexBufferResourceBarrier =
				CD3DX12_RESOURCE_BARRIER::Transition(m_indexBuffer.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_INDEX_BUFFER);
			m_commandList->ResourceBarrier(1, &indexBufferResourceBarrier);
		}

		// Create a descriptor heap for the constant buffers.
		{
			D3D12_DESCRIPTOR_HEAP_DESC heapDesc = {};
			heapDesc.NumDescriptors = DX::c_frameCount;
			heapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
			// This flag indicates that this descriptor heap can be bound to the pipeline and that descriptors contained in it can be referenced by a root table.
			heapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
			DX::ThrowIfFailed(d3dDevice->CreateDescriptorHeap(&heapDesc, IID_PPV_ARGS(&m_cbvHeap)));

            NAME_D3D12_OBJECT(m_cbvHeap);
		}

		CD3DX12_RESOURCE_DESC constantBufferDesc = CD3DX12_RESOURCE_DESC::Buffer(DX::c_frameCount * c_alignedConstantBufferSize);
		DX::ThrowIfFailed(d3dDevice->CreateCommittedResource(
			&uploadHeapProperties,
			D3D12_HEAP_FLAG_NONE,
			&constantBufferDesc,
			D3D12_RESOURCE_STATE_GENERIC_READ,
			nullptr,
			IID_PPV_ARGS(&m_constantBuffer)));

        NAME_D3D12_OBJECT(m_constantBuffer);

		// Create constant buffer views to access the upload buffer.
		D3D12_GPU_VIRTUAL_ADDRESS cbvGpuAddress = m_constantBuffer->GetGPUVirtualAddress();
		CD3DX12_CPU_DESCRIPTOR_HANDLE cbvCpuHandle(m_cbvHeap->GetCPUDescriptorHandleForHeapStart());
		m_cbvDescriptorSize = d3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

		for (int n = 0; n < DX::c_frameCount; n++)
		{
			D3D12_CONSTANT_BUFFER_VIEW_DESC desc;
			desc.BufferLocation = cbvGpuAddress;
			desc.SizeInBytes = c_alignedConstantBufferSize;
			d3dDevice->CreateConstantBufferView(&desc, cbvCpuHandle);

			cbvGpuAddress += desc.SizeInBytes;
			cbvCpuHandle.Offset(m_cbvDescriptorSize);
		}

		// Map the constant buffers.
		CD3DX12_RANGE readRange(0, 0);		// We do not intend to read from this resource on the CPU.
		DX::ThrowIfFailed(m_constantBuffer->Map(0, &readRange, reinterpret_cast<void**>(&m_mappedConstantBuffer)));
		ZeroMemory(m_mappedConstantBuffer, DX::c_frameCount * c_alignedConstantBufferSize);
		// We don't unmap this until the app closes. Keeping things mapped for the lifetime of the resource is okay.

		// Close the command list and execute it to begin the vertex/index buffer copy into the GPU's default heap.
		DX::ThrowIfFailed(m_commandList->Close());
		ID3D12CommandList* ppCommandLists[] = { m_commandList.Get() };
		m_deviceResources->GetCommandQueue()->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);

		// Create vertex/index buffer views.
		m_vertexBufferView.BufferLocation = m_vertexBuffer->GetGPUVirtualAddress();
		m_vertexBufferView.StrideInBytes = sizeof(VertexPositionColor);
		m_vertexBufferView.SizeInBytes = sizeof(cubeVertices);

		m_indexBufferView.BufferLocation = m_indexBuffer->GetGPUVirtualAddress();
		m_indexBufferView.SizeInBytes = sizeof(cubeIndices);
		m_indexBufferView.Format = DXGI_FORMAT_R16_UINT;

		// Wait for the command list to finish executing; the vertex/index buffers need to be uploaded to the GPU before the upload resources go out of scope.
		m_deviceResources->WaitForGpu();
	});

이 함수도 복잡하게 작성되어 있으니 나누어서 살펴보자.

더보기

제일 먼저 필요한 변수를 선언하고,  Command List를 생성한다.

auto d3dDevice = m_deviceResources->GetD3DDevice();

// Create a command list.
DX::ThrowIfFailed(d3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_deviceResources->GetCommandAllocator(), m_pipelineState.Get(), IID_PPV_ARGS(&m_commandList)));
NAME_D3D12_OBJECT(m_commandList);

 

그 다음에는 Cube의 Vertex들을 선언한다.

각 Vertex들은 위치와 색 정보를 가지고 있다.

// Cube vertices. Each vertex has a position and a color.
VertexPositionColor cubeVertices[] =
{
	{ XMFLOAT3(-0.5f, -0.5f, -0.5f), XMFLOAT3(0.0f, 0.0f, 0.0f) },
	{ XMFLOAT3(-0.5f, -0.5f,  0.5f), XMFLOAT3(0.0f, 0.0f, 1.0f) },
	{ XMFLOAT3(-0.5f,  0.5f, -0.5f), XMFLOAT3(0.0f, 1.0f, 0.0f) },
	{ XMFLOAT3(-0.5f,  0.5f,  0.5f), XMFLOAT3(0.0f, 1.0f, 1.0f) },
	{ XMFLOAT3(0.5f, -0.5f, -0.5f), XMFLOAT3(1.0f, 0.0f, 0.0f) },
	{ XMFLOAT3(0.5f, -0.5f,  0.5f), XMFLOAT3(1.0f, 0.0f, 1.0f) },
	{ XMFLOAT3(0.5f,  0.5f, -0.5f), XMFLOAT3(1.0f, 1.0f, 0.0f) },
	{ XMFLOAT3(0.5f,  0.5f,  0.5f), XMFLOAT3(1.0f, 1.0f, 1.0f) },
};

const UINT vertexBufferSize = sizeof(cubeVertices);

 

Cube Vertex가 선언되면 GPU의 default heap 메모리에 vertex buffer resource를 생성하고

vertex 정보를 uplaod heap을 이용해 복사한다.

upload resource는 반드시 gpu 연산이 모두 끝난 후 해제 되어야 한다.

Microsoft::WRL::ComPtr<ID3D12Resource> vertexBufferUpload;

CD3DX12_HEAP_PROPERTIES defaultHeapProperties(D3D12_HEAP_TYPE_DEFAULT);
CD3DX12_RESOURCE_DESC vertexBufferDesc = CD3DX12_RESOURCE_DESC::Buffer(vertexBufferSize);
DX::ThrowIfFailed(d3dDevice->CreateCommittedResource(
	&defaultHeapProperties,
	D3D12_HEAP_FLAG_NONE,
	&vertexBufferDesc,
	D3D12_RESOURCE_STATE_COPY_DEST,
	nullptr,
	IID_PPV_ARGS(&m_vertexBuffer)));

CD3DX12_HEAP_PROPERTIES uploadHeapProperties(D3D12_HEAP_TYPE_UPLOAD);
DX::ThrowIfFailed(d3dDevice->CreateCommittedResource(
	&uploadHeapProperties,
	D3D12_HEAP_FLAG_NONE,
	&vertexBufferDesc,
	D3D12_RESOURCE_STATE_GENERIC_READ,
	nullptr,
	IID_PPV_ARGS(&vertexBufferUpload)));

NAME_D3D12_OBJECT(m_vertexBuffer);

// Upload the vertex buffer to the GPU.
{
	D3D12_SUBRESOURCE_DATA vertexData = {};
	vertexData.pData = reinterpret_cast<BYTE*>(cubeVertices);
	vertexData.RowPitch = vertexBufferSize;
	vertexData.SlicePitch = vertexData.RowPitch;

	UpdateSubresources(m_commandList.Get(), m_vertexBuffer.Get(), vertexBufferUpload.Get(), 0, 0, 1, &vertexData);

	CD3DX12_RESOURCE_BARRIER vertexBufferResourceBarrier =
		CD3DX12_RESOURCE_BARRIER::Transition(m_vertexBuffer.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER);
	m_commandList->ResourceBarrier(1, &vertexBufferResourceBarrier);
}

 

Vertex 정보가 GPU에 올라가면 이번에는 Mesh Index 차례이다.

각 Vertex의 index 3개로 하나의 삼각형을 화면에 출력한다.

// Load mesh indices. Each trio of indices represents a triangle to be rendered on the screen.
// For example: 0,2,1 means that the vertices with indexes 0, 2 and 1 from the vertex buffer compose the
// first triangle of this mesh.
unsigned short cubeIndices[] =
{
	0, 2, 1, // -x
	1, 2, 3,

	4, 5, 6, // +x
	5, 7, 6,

	0, 1, 5, // -y
	0, 5, 4,

	2, 6, 7, // +y
	2, 7, 3,

	0, 4, 6, // -z
	0, 6, 2,

	1, 3, 7, // +z
	1, 7, 5,
};

const UINT indexBufferSize = sizeof(cubeIndices);

 

Mesh Index가 모두 선언 되면 GPU의 default heap에 이 index들을 담을 buffer를 생성하고,

upload heap을 통해 값을 복사한다.
uplaod resource는 vertex와 마찬가지로 GPU에서 사용을 마친 후 반드시 해제되어야 한다.

// Create the index buffer resource in the GPU's default heap and copy index data into it using the upload heap.
// The upload resource must not be released until after the GPU has finished using it.
Microsoft::WRL::ComPtr<ID3D12Resource> indexBufferUpload;

CD3DX12_RESOURCE_DESC indexBufferDesc = CD3DX12_RESOURCE_DESC::Buffer(indexBufferSize);
DX::ThrowIfFailed(d3dDevice->CreateCommittedResource(
	&defaultHeapProperties,
	D3D12_HEAP_FLAG_NONE,
	&indexBufferDesc,
	D3D12_RESOURCE_STATE_COPY_DEST,
	nullptr,
	IID_PPV_ARGS(&m_indexBuffer)));

DX::ThrowIfFailed(d3dDevice->CreateCommittedResource(
	&uploadHeapProperties,
	D3D12_HEAP_FLAG_NONE,
	&indexBufferDesc,
	D3D12_RESOURCE_STATE_GENERIC_READ,
	nullptr,
	IID_PPV_ARGS(&indexBufferUpload)));

NAME_D3D12_OBJECT(m_indexBuffer);

// Upload the index buffer to the GPU.
{
	D3D12_SUBRESOURCE_DATA indexData = {};
	indexData.pData = reinterpret_cast<BYTE*>(cubeIndices);
	indexData.RowPitch = indexBufferSize;
	indexData.SlicePitch = indexData.RowPitch;

	UpdateSubresources(m_commandList.Get(), m_indexBuffer.Get(), indexBufferUpload.Get(), 0, 0, 1, &indexData);

	CD3DX12_RESOURCE_BARRIER indexBufferResourceBarrier =
		CD3DX12_RESOURCE_BARRIER::Transition(m_indexBuffer.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_INDEX_BUFFER);
	m_commandList->ResourceBarrier(1, &indexBufferResourceBarrier);
}

 

Index까지 모두 GPU의 Default Heap에 복사되면 constant buffer를 위한 descriptor heap을 생성한다.

// Create a descriptor heap for the constant buffers.
{
	D3D12_DESCRIPTOR_HEAP_DESC heapDesc = {};
	heapDesc.NumDescriptors = DX::c_frameCount;
	heapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
	// This flag indicates that this descriptor heap can be bound to the pipeline and that descriptors contained in it can be referenced by a root table.
	heapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
	DX::ThrowIfFailed(d3dDevice->CreateDescriptorHeap(&heapDesc, IID_PPV_ARGS(&m_cbvHeap)));

    NAME_D3D12_OBJECT(m_cbvHeap);
}

CD3DX12_RESOURCE_DESC constantBufferDesc = CD3DX12_RESOURCE_DESC::Buffer(DX::c_frameCount * c_alignedConstantBufferSize);
DX::ThrowIfFailed(d3dDevice->CreateCommittedResource(
	&uploadHeapProperties,
	D3D12_HEAP_FLAG_NONE,
	&constantBufferDesc,
	D3D12_RESOURCE_STATE_GENERIC_READ,
	nullptr,
	IID_PPV_ARGS(&m_constantBuffer)));

NAME_D3D12_OBJECT(m_constantBuffer);

 

그리고 upload buffer에 접속하기 위한 constant buffer view를 생성하고, constant buffer를 연결시킨다.

// Create constant buffer views to access the upload buffer.
D3D12_GPU_VIRTUAL_ADDRESS cbvGpuAddress = m_constantBuffer->GetGPUVirtualAddress();
CD3DX12_CPU_DESCRIPTOR_HANDLE cbvCpuHandle(m_cbvHeap->GetCPUDescriptorHandleForHeapStart());
m_cbvDescriptorSize = d3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

for (int n = 0; n < DX::c_frameCount; n++)
{
	D3D12_CONSTANT_BUFFER_VIEW_DESC desc;
	desc.BufferLocation = cbvGpuAddress;
	desc.SizeInBytes = c_alignedConstantBufferSize;
	d3dDevice->CreateConstantBufferView(&desc, cbvCpuHandle);

	cbvGpuAddress += desc.SizeInBytes;
	cbvCpuHandle.Offset(m_cbvDescriptorSize);
}

// Map the constant buffers.
CD3DX12_RANGE readRange(0, 0);		// We do not intend to read from this resource on the CPU.
DX::ThrowIfFailed(m_constantBuffer->Map(0, &readRange, reinterpret_cast<void**>(&m_mappedConstantBuffer)));
ZeroMemory(m_mappedConstantBuffer, DX::c_frameCount * c_alignedConstantBufferSize);
// We don't unmap this until the app closes. Keeping things mapped for the lifetime of the resource is okay.

이제 우리는 이 값을 CPU에서 읽으려 할 필요가 없다.
또한 app이 닫힐 때까지 이 연결을 해제하지 않는다.

 

constant buffer를 view와 연결하면 command list를 닫고, 안에 있는 명령들을 모두 실행한다.

그리고 vertex buffer와 index buffer에도 마찬가지로 view를 생성한다.

// Close the command list and execute it to begin the vertex/index buffer copy into the GPU's default heap.
DX::ThrowIfFailed(m_commandList->Close());
ID3D12CommandList* ppCommandLists[] = { m_commandList.Get() };
m_deviceResources->GetCommandQueue()->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);

// Create vertex/index buffer views.
m_vertexBufferView.BufferLocation = m_vertexBuffer->GetGPUVirtualAddress();
m_vertexBufferView.StrideInBytes = sizeof(VertexPositionColor);
m_vertexBufferView.SizeInBytes = sizeof(cubeVertices);

m_indexBufferView.BufferLocation = m_indexBuffer->GetGPUVirtualAddress();
m_indexBufferView.SizeInBytes = sizeof(cubeIndices);
m_indexBufferView.Format = DXGI_FORMAT_R16_UINT;

 

여기까지 작업이 완료 되었으면, GPU에서 연산이 종료 될 때까지 기다린다.

// Wait for the command list to finish executing; the vertex/index buffers need to be uploaded to the GPU before the upload resources go out of scope.
m_deviceResources->WaitForGpu();

 

작업이 모두 마쳐진 뒤에는 load가 완료 되었다는 의미에서 m_loadingComplete를 true로 바꿔준다.

createAssetsTask.then([this]() {
		m_loadingComplete = true;
	});

 

DeviceDependentResources를 생성하고 나면 이번에는 CreateWindowSizeDependentResources() 차례이다.

이는 화면에 출력하는데 필요한 정보들을 app의 window 크기가 바뀔 때마다 새로 잡아주는 함수이다.

// Initializes view parameters when the window size changes.
void Sample3DSceneRenderer::CreateWindowSizeDependentResources()
{
	Size outputSize = m_deviceResources->GetOutputSize();
	float aspectRatio = outputSize.Width / outputSize.Height;
	float fovAngleY = 70.0f * XM_PI / 180.0f;

	D3D12_VIEWPORT viewport = m_deviceResources->GetScreenViewport();
	m_scissorRect = { 0, 0, static_cast<LONG>(viewport.Width), static_cast<LONG>(viewport.Height)};

	// This is a simple example of change that can be made when the app is in
	// portrait or snapped view.
	if (aspectRatio < 1.0f)
	{
		fovAngleY *= 2.0f;
	}

	// Note that the OrientationTransform3D matrix is post-multiplied here
	// in order to correctly orient the scene to match the display orientation.
	// This post-multiplication step is required for any draw calls that are
	// made to the swap chain render target. For draw calls to other targets,
	// this transform should not be applied.

	// This sample makes use of a right-handed coordinate system using row-major matrices.
	XMMATRIX perspectiveMatrix = XMMatrixPerspectiveFovRH(
		fovAngleY,
		aspectRatio,
		0.01f,
		100.0f
		);

	XMFLOAT4X4 orientation = m_deviceResources->GetOrientationTransform3D();
	XMMATRIX orientationMatrix = XMLoadFloat4x4(&orientation);

	XMStoreFloat4x4(
		&m_constantBufferData.projection,
		XMMatrixTranspose(perspectiveMatrix * orientationMatrix)
		);

	// Eye is at (0,0.7,1.5), looking at point (0,-0.1,0) with the up-vector along the y-axis.
	static const XMVECTORF32 eye = { 0.0f, 0.7f, 1.5f, 0.0f };
	static const XMVECTORF32 at = { 0.0f, -0.1f, 0.0f, 0.0f };
	static const XMVECTORF32 up = { 0.0f, 1.0f, 0.0f, 0.0f };

	XMStoreFloat4x4(&m_constantBufferData.view, XMMatrixTranspose(XMMatrixLookAtRH(eye, at, up)));
}

 

기나긴 생성자 부분을 살펴 보았고, 이제 소멸자를 살펴봅시다.

Sample3DSceneRenderer::~Sample3DSceneRenderer()
{
	m_constantBuffer->Unmap(0, nullptr);
	m_mappedConstantBuffer = nullptr;
}

우선 생성자의 CreateDeviceDependentResources에서 mapping 했던 m_constantBuffer를 해제하고 있습니다.

또한 m_mappedConstantBuffer도 nullptr로 접근하지 못하도록 하였습니다.

 

이제 남은 함수 중 Render에 대해 먼저 보도록 합시다.

// Renders one frame using the vertex and pixel shaders.
bool Sample3DSceneRenderer::Render()
{
	// Loading is asynchronous. Only draw geometry after it's loaded.
	if (!m_loadingComplete)
	{
		return false;
	}

	DX::ThrowIfFailed(m_deviceResources->GetCommandAllocator()->Reset());

	// The command list can be reset anytime after ExecuteCommandList() is called.
	DX::ThrowIfFailed(m_commandList->Reset(m_deviceResources->GetCommandAllocator(), m_pipelineState.Get()));

	PIXBeginEvent(m_commandList.Get(), 0, L"Draw the cube");
	{
		// Set the graphics root signature and descriptor heaps to be used by this frame.
		m_commandList->SetGraphicsRootSignature(m_rootSignature.Get());
		ID3D12DescriptorHeap* ppHeaps[] = { m_cbvHeap.Get() };
		m_commandList->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps);

		// Bind the current frame's constant buffer to the pipeline.
		CD3DX12_GPU_DESCRIPTOR_HANDLE gpuHandle(m_cbvHeap->GetGPUDescriptorHandleForHeapStart(), m_deviceResources->GetCurrentFrameIndex(), m_cbvDescriptorSize);
		m_commandList->SetGraphicsRootDescriptorTable(0, gpuHandle);

		// Set the viewport and scissor rectangle.
		D3D12_VIEWPORT viewport = m_deviceResources->GetScreenViewport();
		m_commandList->RSSetViewports(1, &viewport);
		m_commandList->RSSetScissorRects(1, &m_scissorRect);

		// Indicate this resource will be in use as a render target.
		CD3DX12_RESOURCE_BARRIER renderTargetResourceBarrier =
			CD3DX12_RESOURCE_BARRIER::Transition(m_deviceResources->GetRenderTarget(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET);
		m_commandList->ResourceBarrier(1, &renderTargetResourceBarrier);

		// Record drawing commands.
		D3D12_CPU_DESCRIPTOR_HANDLE renderTargetView = m_deviceResources->GetRenderTargetView();
		D3D12_CPU_DESCRIPTOR_HANDLE depthStencilView = m_deviceResources->GetDepthStencilView();
		m_commandList->ClearRenderTargetView(renderTargetView, DirectX::Colors::CornflowerBlue, 0, nullptr);
		m_commandList->ClearDepthStencilView(depthStencilView, D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr);

		m_commandList->OMSetRenderTargets(1, &renderTargetView, false, &depthStencilView);

		m_commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
		m_commandList->IASetVertexBuffers(0, 1, &m_vertexBufferView);
		m_commandList->IASetIndexBuffer(&m_indexBufferView);
		m_commandList->DrawIndexedInstanced(36, 1, 0, 0, 0);

		// Indicate that the render target will now be used to present when the command list is done executing.
		CD3DX12_RESOURCE_BARRIER presentResourceBarrier =
			CD3DX12_RESOURCE_BARRIER::Transition(m_deviceResources->GetRenderTarget(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT);
		m_commandList->ResourceBarrier(1, &presentResourceBarrier);
	}
	PIXEndEvent(m_commandList.Get());

	DX::ThrowIfFailed(m_commandList->Close());

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

	return true;
}

간략하게 나누어서 봅시다.

더보기

우선 geometry가 loading 되었는지 m_loadingComplete로 확인합니다.
Render연산은 한번만 호출되더라도 동작 할 때까지 호출 되기 때문에,
loading 되어 있지 않으면 그냥 다음 frame이 될 때까지 아무 작업도 하지 않는다.

// Loading is asynchronous. Only draw geometry after it's loaded.
if (!m_loadingComplete)
{
	return false;
}

 

Geometry가 모두 loading 되어 있다면 Command Allocator와 CommandList를 Reset한다.

만약 이 과정에서 오류가 발생 한다면, 새로운 Geometry는 그려지지 않고 함수 밖으로 에러가 던져진다.

DX::ThrowIfFailed(m_deviceResources->GetCommandAllocator()->Reset());

// The command list can be reset anytime after ExecuteCommandList() is called.
DX::ThrowIfFailed(m_commandList->Reset(m_deviceResources->GetCommandAllocator(), m_pipelineState.Get()));

 

이렇게 Reset 된 CommandList에 PIXEvent를 새로 넣어준다.

PIXBeginEvent(m_commandList.Get(), 0, L"Draw the cube");
	{
		// Set the graphics root signature and descriptor heaps to be used by this frame.
		m_commandList->SetGraphicsRootSignature(m_rootSignature.Get());
		ID3D12DescriptorHeap* ppHeaps[] = { m_cbvHeap.Get() };
		m_commandList->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps);

		// Bind the current frame's constant buffer to the pipeline.
		CD3DX12_GPU_DESCRIPTOR_HANDLE gpuHandle(m_cbvHeap->GetGPUDescriptorHandleForHeapStart(), m_deviceResources->GetCurrentFrameIndex(), m_cbvDescriptorSize);
		m_commandList->SetGraphicsRootDescriptorTable(0, gpuHandle);

		// Set the viewport and scissor rectangle.
		D3D12_VIEWPORT viewport = m_deviceResources->GetScreenViewport();
		m_commandList->RSSetViewports(1, &viewport);
		m_commandList->RSSetScissorRects(1, &m_scissorRect);

		// Indicate this resource will be in use as a render target.
		CD3DX12_RESOURCE_BARRIER renderTargetResourceBarrier =
			CD3DX12_RESOURCE_BARRIER::Transition(m_deviceResources->GetRenderTarget(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET);
		m_commandList->ResourceBarrier(1, &renderTargetResourceBarrier);

		// Record drawing commands.
		D3D12_CPU_DESCRIPTOR_HANDLE renderTargetView = m_deviceResources->GetRenderTargetView();
		D3D12_CPU_DESCRIPTOR_HANDLE depthStencilView = m_deviceResources->GetDepthStencilView();
		m_commandList->ClearRenderTargetView(renderTargetView, DirectX::Colors::CornflowerBlue, 0, nullptr);
		m_commandList->ClearDepthStencilView(depthStencilView, D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr);

		m_commandList->OMSetRenderTargets(1, &renderTargetView, false, &depthStencilView);

		m_commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
		m_commandList->IASetVertexBuffers(0, 1, &m_vertexBufferView);
		m_commandList->IASetIndexBuffer(&m_indexBufferView);
		m_commandList->DrawIndexedInstanced(36, 1, 0, 0, 0);

		// Indicate that the render target will now be used to present when the command list is done executing.
		CD3DX12_RESOURCE_BARRIER presentResourceBarrier =
			CD3DX12_RESOURCE_BARRIER::Transition(m_deviceResources->GetRenderTarget(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT);
		m_commandList->ResourceBarrier(1, &presentResourceBarrier);
	}
	PIXEndEvent(m_commandList.Get());

먼저 Graphic root signature와 descriptor heap을 현재 frame에서 사용하도록 설정한다.
그리고 현재 frame의 constant buffer를 pipeline에 할당한다.
할당이 끝나면 viewport와 scissor rectangle을 설정한다.
여기서 나타낸 resource는 renter target에 사용된다.

이 작업이 모두 마치면 drawing command를 작성한다. 
위에서 언급한 render target은 command list가 완전히 작동되기 전까지 사용되지 않는다.

 

이벤트가 모두 작성이 되면 m_commandList를 닫고, 입력된 이벤트를 실행한다.

DX::ThrowIfFailed(m_commandList->Close());

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

 

 

다음은 Update이다.

Update는 매 프레임마다 호출되며, cube를 회전시키는 동작을 한다.

그리고 이에 맞게 model과 view matrix를 연산하는 작업도 맡느다.

// Called once per frame, rotates the cube and calculates the model and view matrices.
void Sample3DSceneRenderer::Update(DX::StepTimer const& timer)
{
	if (m_loadingComplete)
	{
		if (!m_tracking)
		{
			// Rotate the cube a small amount.
			m_angle += static_cast<float>(timer.GetElapsedSeconds()) * m_radiansPerSecond;

			Rotate(m_angle);
		}

		// Update the constant buffer resource.
		UINT8* destination = m_mappedConstantBuffer + (m_deviceResources->GetCurrentFrameIndex() * c_alignedConstantBufferSize);
		memcpy(destination, &m_constantBufferData, sizeof(m_constantBufferData));
	}
}

이 연산은 Geometry가 모두 load 되어 있어야 하며, tracking 상태가 아니면 Rotate 함수를 호출하여 회전을 시킨다.

여기서 호출하는 Rotate는 단순히 XMStoreFloat4x4를 이용한 연산이다.

// Rotate the 3D cube model a set amount of radians.
void Sample3DSceneRenderer::Rotate(float radians)
{
	// Prepare to pass the updated model matrix to the shader.
	XMStoreFloat4x4(&m_constantBufferData.model, XMMatrixTranspose(XMMatrixRotationY(radians)));
}

연산이 모두 끝나면 갱신된 정보를 constant buffer resource에 갱신한다.

 

마지막으로 소개 할 것은 SaveState이다.

생성자에서 LoadState가 호출 되었듯이, App이 종료될 때에는 SaveState가 호출된다.

// Saves the current state of the renderer.
void Sample3DSceneRenderer::SaveState()
{
	auto state = ApplicationData::Current->LocalSettings->Values;

	if (state->HasKey(AngleKey))
	{
		state->Remove(AngleKey);
	}
	if (state->HasKey(TrackingKey))
	{
		state->Remove(TrackingKey);
	}

	state->Insert(AngleKey, PropertyValue::CreateSingle(m_angle));
	state->Insert(TrackingKey, PropertyValue::CreateBoolean(m_tracking));
}

내부에서 작동은 간단하다.

이전 Key가 있다면 키를 지우고, 새로운 Key를 저장한다.

 

 

이것으로 매우매우 간단하게 Sample3DSceneRenderer를 살펴보았다.

그리고 이걸 보고 느낀 점은, 따라서 못만든다는 것이다.

사용하는 라이브러리서부터 구조까지 아직 내 실력으로 짧은 시간 내에 이해하기 힘든 것들 뿐이다.

너무 잘 짜여져 있어서 제대로 이해하지 못하고 잘못 건드렸다가 더 큰 화를 부를 것 같기도 하고, 

무엇보다 자신이 없다.

흥미가 있기도 하지만 현실적으로 좀 더 습득 속도를 요구하기 때문에

Windows Programming으로 DirectX를 먼저 공부하고, 이 C++/CX를 이용한 방법은 여유가 생길 때 하도록 보류한다.

#include "Common\StepTimer.h"
#include "Common\DeviceResources.h"
#include "Content\Sample3DSceneRenderer.h"

// Renders Direct3D content on the screen.
namespace ExampleCreation
{
	class ExampleCreationMain
	{
	public:
		ExampleCreationMain();
		void CreateRenderers(const std::shared_ptr<DX::DeviceResources>& deviceResources);
		void Update();
		bool Render();

		void OnWindowSizeChanged();
		void OnSuspending();
		void OnResuming();
		void OnDeviceRemoved();

	private:
		// TODO: Replace with your own content renderers.
		std::unique_ptr<Sample3DSceneRenderer> m_sceneRenderer;

		// Rendering loop timer.
		DX::StepTimer m_timer;
	};
}

Direct3D Content를 화면에 출력하는 코드들을 엮은 ExampleCreationMain class이다.

Function은 구현체를 보면서 이야기 할테니, Field들을 살펴보자.

 

StepTimer는 MS에서 제공하는 코드이다.

https://github.com/microsoft/DirectXTK/wiki/StepTimer

 

microsoft/DirectXTK

The DirectX Tool Kit (aka DirectXTK) is a collection of helper classes for writing DirectX 11.x code in C++ - microsoft/DirectXTK

github.com

대부분의 기능은 Tick과 관련되어 있으며, 여타 다른 함수들과 마찬가지로 상시 적용을 해야 하는 부분이다.

 

Sample3DSceneRenderer는 TODO에 적혀있는대로, 화면에 출력 될 내용에 관한 함수이다.

즉, ExampleCreationMain도 결국 주어진 함수를 출력만 하는 코드라는 것이다.

밑에 Function 별 기능에 대해 설명은 하겠지만, 보면 실질적으로 직접 무언가 하는 작업은 없다.

다른 Function 호출이 대부분이다.

 

그런 의미에서 빠르게 Function별 설명으로 넘어가자.

해당 Function이 어떤 기능을 하고, 내부에서 어떤 구현이 있는지를 주로 볼 것이다.

 

// Loads and initializes application assets when the application is loaded.
ExampleCreationMain::ExampleCreationMain()
{
	// TODO: Change the timer settings if you want something other than the default variable timestep mode.
	// e.g. for 60 FPS fixed timestep update logic, call:
	/*
	m_timer.SetFixedTimeStep(true);
	m_timer.SetTargetElapsedSeconds(1.0 / 60);
	*/
}

생성자 안에는 아무런 선언이 없다. 

다만, FPS 고정 수치를 변경하는 등의 기능을 생성자 안에서 어떻게 할 수 있는지 주석으로 설명이 되어 있다.

 

// Creates and initializes the renderers.
void ExampleCreationMain::CreateRenderers(const std::shared_ptr<DX::DeviceResources>& deviceResources)
{
	// TODO: Replace this with your app's content initialization.
	m_sceneRenderer = std::unique_ptr<Sample3DSceneRenderer>(new Sample3DSceneRenderer(deviceResources));

	OnWindowSizeChanged();
}

CreateRenderes는 Renderer를 생성하고 초기화 하는 구문이다.

App class에서 GetDeviceREsources에서 호출이 되는 함수로, Sample3DSceneRenderer 객체를 생성하고,

내용물에 따른 화면 크기 변화에 대비한 OnWindowSizeChanged를 호출한다.

 

// Updates the application state once per frame.
void ExampleCreationMain::Update()
{
	// Update scene objects.
	m_timer.Tick([&]()
	{
		// TODO: Replace this with your app's content update functions.
		m_sceneRenderer->Update(m_timer);
	});
}

Update는 매 프레임마다 바뀌는 위상에 대한 정보를 연산하는 기능을 한다.

App의 Run 함수에서 호출이 되며, m_timer의 Tick 함수에

Sample3DSceneREnderer의 Update 함수에 m_timer를 인자로 넘기는 것을 lambda로 호출한다.

 

// Renders the current frame according to the current application state.
// Returns true if the frame was rendered and is ready to be displayed.
bool ExampleCreationMain::Render()
{
	// Don't try to render anything before the first Update.
	if (m_timer.GetFrameCount() == 0)
	{
		return false;
	}

	// Render the scene objects.
	// TODO: Replace this with your app's content rendering functions.
	return m_sceneRenderer->Render();
}

역시나 App의 Run 함수에서 호출되는 Render는 매 프레임마다 바뀌는 정보로

해당 프레임에 적절한 위상을 변경하는 역할을 한다.

단, 첫 프레임일 때에는 이 작업을 하지 않는다.

 

// Updates application state when the window's size changes (e.g. device orientation change)
void ExampleCreationMain::OnWindowSizeChanged()
{
	// TODO: Replace this with the size-dependent initialization of your app's content.
	m_sceneRenderer->CreateWindowSizeDependentResources();
}

Window 크기가 조절될 때 호출되는 OnWindowSizeChanged는 우리가 구현했던

Sample3DSceneRenderer의 CreateWindowSizeDependentResources를 호출한다.

뒤에 설명을 하겠지만, 이는 화면 크기가 변할 때마다 바뀌어야 할 관련 수치들을 변경해주는 역할을 한다.

 

// Notifies the app that it is being suspended.
void ExampleCreationMain::OnSuspending()
{
	// TODO: Replace this with your app's suspending logic.

	// Process lifetime management may terminate suspended apps at any time, so it is
	// good practice to save any state that will allow the app to restart where it left off.

	m_sceneRenderer->SaveState();

	// If your application uses video memory allocations that are easy to re-create,
	// consider releasing that memory to make it available to other applications.
}

App이 종료 될 때 호출되는 OnSuspending이다.

현재까지 적용된 상태들이 온전히 적용될 수 있도록 저장을 한다.

 

// Notifes the app that it is no longer suspended.
void ExampleCreationMain::OnResuming()
{
	// TODO: Replace this with your app's resuming logic.
}

Suspend 상태가 해제 될 때 호출되는 OnResuming이다.

현재 아무런 구현이 되어있지 않다.

 

// Notifies renderers that device resources need to be released.
void ExampleCreationMain::OnDeviceRemoved()
{
	// TODO: Save any necessary application or renderer state and release the renderer
	// and its resources which are no longer valid.
	m_sceneRenderer->SaveState();
	m_sceneRenderer = nullptr;
}

마지막으로 device resources가 해제 될 때 Renderer에 이벤트를 넣는 OnDeviceRemoved이다.

이 경우에도 Suspend와 마찬가지로 SaveState를 통해 상태저장을 하고, 더 나아가 SceneRender를 해제하기까지 한다.

 

결국 ExampleCreationMain까지도 코드에서 제공하는 일종의 Frame 중 하나였다.

예시만 따라한다면, 무엇 하나 바꿀 필요가 없다. m_sceneRenderer를 제외하고는.

 

이렇게 일주일이 더 지났고, 예시를 적용하기 위해서 우리가 변경해야 할 코드는 Sample3DSceneRenderer로 좁혀졌다.

가능하면 더 시간을 끌고 싶지 않았지만, Sample3DSceneRenderer가 꽤 길고 복잡하다.

게다가 C++/CX를 사용하는 구문도 조금 있어서 지금 컨디션에 바로 하기 힘들 것 같다.

 

때문에 금요일로 조금 미루겠습니다.

오늘은 DirectX12 template의 예시 프로젝트에서 Rendering 부분.

즉, DirectX를 사용하는 부분에 대해 분석을 해볼 차례이다.

그 중 먼저 살펴볼 것은 DeviceResources이다.

 

저번 글에서 DeviceResources는 DirectX에서 제공하는 Device Resource들을 관리한다고 언급했다.

Document에서는 해당 코드를 소유한 Application에 장치 손실 또는 생성에 대한 알림을 받을 수 있는 Interface를 제공한다고 설명하고 있다.

https://docs.microsoft.com/ko-kr/windows/uwp/gaming/user-interface 

 

DirectX 게임 프로젝트 템플릿 - UWP applications

UWP(유니버설 Windows 플랫폼) 및 DirectX 게임을 만드는 템플릿에 대해 알아봅니다.

docs.microsoft.com

https://github.com/Microsoft/DirectXTK/wiki/DeviceResources

 

microsoft/DirectXTK

The DirectX Tool Kit (aka DirectXTK) is a collection of helper classes for writing DirectX 11.x code in C++ - microsoft/DirectXTK

github.com

github wiki에서는 device & swapchain, 선택적 깊이 버퍼에 대한 추상화를 제공한다고 설명하고 있다.

그 외에 설명으로는 단순히 'boilerplate'를 별도의 파일로 정리 한 것이라고 되어 있다.

여기서 boilerplate란, 최소한의 수정만을 거쳐 여러 곳에 필수적으로 사용되는 코드를 지칭한다.

https://en.wikipedia.org/wiki/Boilerplate_code

 

Boilerplate code - Wikipedia

From Wikipedia, the free encyclopedia Jump to navigation Jump to search Code that has to be included in many places with little or no alteration In computer programming, boilerplate code or just boilerplate are sections of code that have to be included in

en.wikipedia.org

그리하여 DeviceResources는 가능하면 건드리지 않는 것이 좋은 코드라는 것을 나름 긴 설명을 통해 깨달았다.

이제 본격적으로 코드를 파헤쳐보자.

#pragma once

namespace DX
{
	static const UINT c_frameCount = 3;		// Use triple buffering.

	// Controls all the DirectX device resources.
	class DeviceResources
	{
	public:
		DeviceResources(DXGI_FORMAT backBufferFormat = DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT depthBufferFormat = DXGI_FORMAT_D32_FLOAT);
		void SetWindow(Windows::UI::Core::CoreWindow^ window);
		void SetLogicalSize(Windows::Foundation::Size logicalSize);
		void SetCurrentOrientation(Windows::Graphics::Display::DisplayOrientations currentOrientation);
		void SetDpi(float dpi);
		void ValidateDevice();
		void Present();
		void WaitForGpu();

		// The size of the render target, in pixels.
		Windows::Foundation::Size	GetOutputSize() const				{ return m_outputSize; }

		// The size of the render target, in dips.
		Windows::Foundation::Size	GetLogicalSize() const				{ return m_logicalSize; }

		float						GetDpi() const						{ return m_effectiveDpi; }
		bool						IsDeviceRemoved() const				{ return m_deviceRemoved; }

		// D3D Accessors.
		ID3D12Device*				GetD3DDevice() const				{ return m_d3dDevice.Get(); }
		IDXGISwapChain3*			GetSwapChain() const				{ return m_swapChain.Get(); }
		ID3D12Resource*				GetRenderTarget() const				{ return m_renderTargets[m_currentFrame].Get(); }
		ID3D12Resource*				GetDepthStencil() const				{ return m_depthStencil.Get(); }
		ID3D12CommandQueue*			GetCommandQueue() const				{ return m_commandQueue.Get(); }
		ID3D12CommandAllocator*		GetCommandAllocator() const			{ return m_commandAllocators[m_currentFrame].Get(); }
		DXGI_FORMAT					GetBackBufferFormat() const			{ return m_backBufferFormat; }
		DXGI_FORMAT					GetDepthBufferFormat() const		{ return m_depthBufferFormat; }
		D3D12_VIEWPORT				GetScreenViewport() const			{ return m_screenViewport; }
		DirectX::XMFLOAT4X4			GetOrientationTransform3D() const	{ return m_orientationTransform3D; }
		UINT						GetCurrentFrameIndex() const		{ return m_currentFrame; }

		CD3DX12_CPU_DESCRIPTOR_HANDLE GetRenderTargetView() const
		{
			return CD3DX12_CPU_DESCRIPTOR_HANDLE(m_rtvHeap->GetCPUDescriptorHandleForHeapStart(), m_currentFrame, m_rtvDescriptorSize);
		}
		CD3DX12_CPU_DESCRIPTOR_HANDLE GetDepthStencilView() const
		{
			return CD3DX12_CPU_DESCRIPTOR_HANDLE(m_dsvHeap->GetCPUDescriptorHandleForHeapStart());
		}

	private:
		void CreateDeviceIndependentResources();
		void CreateDeviceResources();
		void CreateWindowSizeDependentResources();
		void UpdateRenderTargetSize();
		void MoveToNextFrame();
		DXGI_MODE_ROTATION ComputeDisplayRotation();
		void GetHardwareAdapter(IDXGIAdapter1** ppAdapter);

		UINT											m_currentFrame;

		// Direct3D objects.
		Microsoft::WRL::ComPtr<ID3D12Device>			m_d3dDevice;
		Microsoft::WRL::ComPtr<IDXGIFactory4>			m_dxgiFactory;
		Microsoft::WRL::ComPtr<IDXGISwapChain3>			m_swapChain;
		Microsoft::WRL::ComPtr<ID3D12Resource>			m_renderTargets[c_frameCount];
		Microsoft::WRL::ComPtr<ID3D12Resource>			m_depthStencil;
		Microsoft::WRL::ComPtr<ID3D12DescriptorHeap>	m_rtvHeap;
		Microsoft::WRL::ComPtr<ID3D12DescriptorHeap>	m_dsvHeap;
		Microsoft::WRL::ComPtr<ID3D12CommandQueue>		m_commandQueue;
		Microsoft::WRL::ComPtr<ID3D12CommandAllocator>	m_commandAllocators[c_frameCount];
		DXGI_FORMAT										m_backBufferFormat;
		DXGI_FORMAT										m_depthBufferFormat;
		D3D12_VIEWPORT									m_screenViewport;
		UINT											m_rtvDescriptorSize;
		bool											m_deviceRemoved;

		// CPU/GPU Synchronization.
		Microsoft::WRL::ComPtr<ID3D12Fence>				m_fence;
		UINT64											m_fenceValues[c_frameCount];
		HANDLE											m_fenceEvent;

		// Cached reference to the Window.
		Platform::Agile<Windows::UI::Core::CoreWindow>	m_window;

		// Cached device properties.
		Windows::Foundation::Size						m_d3dRenderTargetSize;
		Windows::Foundation::Size						m_outputSize;
		Windows::Foundation::Size						m_logicalSize;
		Windows::Graphics::Display::DisplayOrientations	m_nativeOrientation;
		Windows::Graphics::Display::DisplayOrientations	m_currentOrientation;
		float											m_dpi;

		// This is the DPI that will be reported back to the app. It takes into account whether the app supports high resolution screens or not.
		float											m_effectiveDpi;

		// Transforms used for display orientation.
		DirectX::XMFLOAT4X4								m_orientationTransform3D;
	};
}

DeviceResources.h이다.

Method 중 'Get'으로 시작하거나 'Is'로 시작하는 method, 'Set'으로 시작하는 method들은 설명을 생략하겠다.

이들은 대체로 field들의 Getter, Setter 함수들이기에 값을 받아오거나 설정하는 기능으로만 기억하면 된다.

중요한 것은 이런 method가 아니라 그것이 가르키는 field가 무엇인지 아니겠는가?

 

#include "pch.h"
#include "DeviceResources.h"
#include "DirectXHelper.h"

using namespace DirectX;
using namespace Microsoft::WRL;
using namespace Windows::Foundation;
using namespace Windows::Graphics::Display;
using namespace Windows::UI::Core;
using namespace Windows::UI::Xaml::Controls;
using namespace Platform;

namespace DisplayMetrics
{
	// High resolution displays can require a lot of GPU and battery power to render.
	// High resolution phones, for example, may suffer from poor battery life if
	// games attempt to render at 60 frames per second at full fidelity.
	// The decision to render at full fidelity across all platforms and form factors
	// should be deliberate.
	static const bool SupportHighResolutions = false;

	// The default thresholds that define a "high resolution" display. If the thresholds
	// are exceeded and SupportHighResolutions is false, the dimensions will be scaled
	// by 50%.
	static const float DpiThreshold = 192.0f;		// 200% of standard desktop display.
	static const float WidthThreshold = 1920.0f;	// 1080p width.
	static const float HeightThreshold = 1080.0f;	// 1080p height.
};

// Constants used to calculate screen rotations.
namespace ScreenRotation
{
	// 0-degree Z-rotation
	static const XMFLOAT4X4 Rotation0(
		1.0f, 0.0f, 0.0f, 0.0f,
		0.0f, 1.0f, 0.0f, 0.0f,
		0.0f, 0.0f, 1.0f, 0.0f,
		0.0f, 0.0f, 0.0f, 1.0f
		);

	// 90-degree Z-rotation
	static const XMFLOAT4X4 Rotation90(
		0.0f, 1.0f, 0.0f, 0.0f,
		-1.0f, 0.0f, 0.0f, 0.0f,
		0.0f, 0.0f, 1.0f, 0.0f,
		0.0f, 0.0f, 0.0f, 1.0f
		);

	// 180-degree Z-rotation
	static const XMFLOAT4X4 Rotation180(
		-1.0f, 0.0f, 0.0f, 0.0f,
		0.0f, -1.0f, 0.0f, 0.0f,
		0.0f, 0.0f, 1.0f, 0.0f,
		0.0f, 0.0f, 0.0f, 1.0f
		);

	// 270-degree Z-rotation
	static const XMFLOAT4X4 Rotation270(
		0.0f, -1.0f, 0.0f, 0.0f,
		1.0f, 0.0f, 0.0f, 0.0f,
		0.0f, 0.0f, 1.0f, 0.0f,
		0.0f, 0.0f, 0.0f, 1.0f
		);
};

DeviceResources.cpp에서 header나 namespace 선언, 상수 선언 부분만 가져온 것이다.

 

먼저 DisplayMatrics namespace 안에 선언된 상수들을 살펴보자.

SupportHighResolutions는 High Resolution을 지원하는지를 판단하는 변수이다.

High Resolution은 많은 GPU resource와 전력을 소모하는 작업이다.

그렇기에 예시에서는 이를 지원하지 않는 것으로 결정 한 것 같다.

WidthThreshold, HeightThreshold는 화면의 너비와 높이 크기 제한을 결정한다.

DpiThreshold 역시 해상도의 제한을 결정하며, 192.0은 보통 데스크탑 화면의 200% 정도이다.

 

ScreenRotation은 4×4 Matrix 상에서 Z축을 기준으로 0, 90, 180, 360 회전 연산을 Matrix로 표현한 것이다.

 

이 중 다시 한번 낯선 것이 있다.

Header에 보면 [DirectXHelper.h]라는 header 파일이 보일 것이다.

DirectXHelper.h는 Microsoft에서 제공하는 DirectX 개발을 지원하는 코드이다.

#pragma once

#include <ppltasks.h>	// For create_task

namespace DX
{
	inline void ThrowIfFailed(HRESULT hr)
	{
		if (FAILED(hr))
		{
			// Set a breakpoint on this line to catch Win32 API errors.
			throw Platform::Exception::CreateException(hr);
		}
	}

	// Function that reads from a binary file asynchronously.
	inline Concurrency::task<std::vector<byte>> ReadDataAsync(const std::wstring& filename)
	{
		using namespace Windows::Storage;
		using namespace Concurrency;

		auto folder = Windows::ApplicationModel::Package::Current->InstalledLocation;

		return create_task(folder->GetFileAsync(Platform::StringReference(filename.c_str()))).then([](StorageFile^ file)
		{
			return FileIO::ReadBufferAsync(file);
		}).then([](Streams::IBuffer^ fileBuffer) -> std::vector<byte>
		{
			std::vector<byte> returnBuffer;
			returnBuffer.resize(fileBuffer->Length);
			Streams::DataReader::FromBuffer(fileBuffer)->ReadBytes(Platform::ArrayReference<byte>(returnBuffer.data(), fileBuffer->Length));
			return returnBuffer;
		});
	}

	// Converts a length in device-independent pixels (DIPs) to a length in physical pixels.
	inline float ConvertDipsToPixels(float dips, float dpi)
	{
		static const float dipsPerInch = 96.0f;
		return floorf(dips * dpi / dipsPerInch + 0.5f); // Round to nearest integer.
	}

	// Assign a name to the object to aid with debugging.
#if defined(_DEBUG)
	inline void SetName(ID3D12Object* pObject, LPCWSTR name)
	{
		pObject->SetName(name);
	}
#else
	inline void SetName(ID3D12Object*, LPCWSTR)
	{
	}
#endif
}

// Naming helper function for ComPtr<T>.
// Assigns the name of the variable as the name of the object.
#define NAME_D3D12_OBJECT(x) DX::SetName(x.Get(), L#x)

ThrowIfFailed는 DirectX Win32 API에서 반환된 오류 HRESULT 값을 Windows Runtime Exception으로 변환한다.

이를 이용해 DirectX Error를 Debug 하기 위한 중단점을 배치한다.

https://github.com/Microsoft/DirectXTK/wiki/ThrowIfFailed

 

microsoft/DirectXTK

The DirectX Tool Kit (aka DirectXTK) is a collection of helper classes for writing DirectX 11.x code in C++ - microsoft/DirectXTK

github.com

ReadDataAsync는 binary file을 비동기적으로 읽는 함수이다.

마지막으로 ConvertDipsToPixels는 DIP의 길이를 실제 Pixel의 길이로 변환하는 함수이다.

 

다음은 DeviceResources의 생성자 부분이다.

// Constructor for DeviceResources.
DX::DeviceResources::DeviceResources(DXGI_FORMAT backBufferFormat, DXGI_FORMAT depthBufferFormat) :
	m_currentFrame(0),
	m_screenViewport(),
	m_rtvDescriptorSize(0),
	m_fenceEvent(0),
	m_backBufferFormat(backBufferFormat),
	m_depthBufferFormat(depthBufferFormat),
	m_fenceValues{},
	m_d3dRenderTargetSize(),
	m_outputSize(),
	m_logicalSize(),
	m_nativeOrientation(DisplayOrientations::None),
	m_currentOrientation(DisplayOrientations::None),
	m_dpi(-1.0f),
	m_effectiveDpi(-1.0f),
	m_deviceRemoved(false)
{
	CreateDeviceIndependentResources();
	CreateDeviceResources();
}

// Configures resources that don't depend on the Direct3D device.
void DX::DeviceResources::CreateDeviceIndependentResources()
{
}

생성자에서는 크게 2가지 작업이 이루어지고 있다.

하나는 field의 초기화. 다른 하나는 Resources를 생성하고 있다.

Resource는 두가지로 분류된다. Direct3D device와 독립적인 것과, 연관된 것.

본 예시는 DirectX12를 사용하기 대문에 독립적인 부분인 CreateDeviceIndependentResources는 빈 함수이다.

 

그렇다면 남은 함수인 CreateDeviceResources를 보자.

// Configures the Direct3D device, and stores handles to it and the device context.
void DX::DeviceResources::CreateDeviceResources()
{
#if defined(_DEBUG)
	// If the project is in a debug build, enable debugging via SDK Layers.
	{
		ComPtr<ID3D12Debug> debugController;
		if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController))))
		{
			debugController->EnableDebugLayer();
		}
	}
#endif

	DX::ThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(&m_dxgiFactory)));

	ComPtr<IDXGIAdapter1> adapter;
	GetHardwareAdapter(&adapter);

	// Create the Direct3D 12 API device object
	HRESULT hr = D3D12CreateDevice(
		adapter.Get(),					// The hardware adapter.
		D3D_FEATURE_LEVEL_11_0,			// Minimum feature level this app can support.
		IID_PPV_ARGS(&m_d3dDevice)		// Returns the Direct3D device created.
		);

#if defined(_DEBUG)
	if (FAILED(hr))
	{
		// If the initialization fails, fall back to the WARP device.
		// For more information on WARP, see: 
		// https://go.microsoft.com/fwlink/?LinkId=286690

		ComPtr<IDXGIAdapter> warpAdapter;
		DX::ThrowIfFailed(m_dxgiFactory->EnumWarpAdapter(IID_PPV_ARGS(&warpAdapter)));

		hr = D3D12CreateDevice(warpAdapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&m_d3dDevice));
	}
#endif

	DX::ThrowIfFailed(hr);

	// Create the command queue.
	D3D12_COMMAND_QUEUE_DESC queueDesc = {};
	queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
	queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;

	DX::ThrowIfFailed(m_d3dDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&m_commandQueue)));
	NAME_D3D12_OBJECT(m_commandQueue);

	// Create descriptor heaps for render target views and depth stencil views.
	D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {};
	rtvHeapDesc.NumDescriptors = c_frameCount;
	rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
	rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
	DX::ThrowIfFailed(m_d3dDevice->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(&m_rtvHeap)));
	NAME_D3D12_OBJECT(m_rtvHeap);

	m_rtvDescriptorSize = m_d3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);

	D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc = {};
	dsvHeapDesc.NumDescriptors = 1;
	dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
	dsvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
	ThrowIfFailed(m_d3dDevice->CreateDescriptorHeap(&dsvHeapDesc, IID_PPV_ARGS(&m_dsvHeap)));
	NAME_D3D12_OBJECT(m_dsvHeap);

	for (UINT n = 0; n < c_frameCount; n++)
	{
		DX::ThrowIfFailed(
			m_d3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&m_commandAllocators[n]))
			);
	}

	// Create synchronization objects.
	DX::ThrowIfFailed(m_d3dDevice->CreateFence(m_fenceValues[m_currentFrame], D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_fence)));
	m_fenceValues[m_currentFrame]++;

	m_fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
	if (m_fenceEvent == nullptr)
	{
		DX::ThrowIfFailed(HRESULT_FROM_WIN32(GetLastError()));
	}
}

간단하게 설명을 하자면

1) DXGIFactory1을 생성하면서 실패 시 Error를 throw 한다.

2) 1)이 문제 없이 동작하면 Comptr<IDXGIAdapter1>에 GetHardwareAdapter의 반환 값을 저장한다.

GetHardWareAdpater는 Direct3D 12를 제공하는 사용 가능한 Hardware Adapter 중 첫번째를 반환한다.

// This method acquires the first available hardware adapter that supports Direct3D 12.
// If no such adapter can be found, *ppAdapter will be set to nullptr.
void DX::DeviceResources::GetHardwareAdapter(IDXGIAdapter1** ppAdapter)
{
	ComPtr<IDXGIAdapter1> adapter;
	*ppAdapter = nullptr;

	for (UINT adapterIndex = 0; DXGI_ERROR_NOT_FOUND != m_dxgiFactory->EnumAdapters1(adapterIndex, &adapter); adapterIndex++)
	{
		DXGI_ADAPTER_DESC1 desc;
		adapter->GetDesc1(&desc);

		if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
		{
			// Don't select the Basic Render Driver adapter.
			continue;
		}

		// Check to see if the adapter supports Direct3D 12, but don't create the
		// actual device yet.
		if (SUCCEEDED(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0, _uuidof(ID3D12Device), nullptr)))
		{
			break;
		}
	}

	*ppAdapter = adapter.Detach();
}

3) 2)에서 저장한 HardwareAdapter와 이 Application이 제공 할 최소 Direct3D feature level, 생성 될 Direct3D device를 저장 할 변수를 넣어 Direct3D 12 API Device Object를 생성한다.

4) 3)에서 생성한 Device Object 값이 유효한지 검사한다.

5) command queue를 생성한다.

6) render target views와 depth stencil vies에 사용될 descriptor heap을 생성한다.

7) 동기화 오브젝트를 생성한다.

 

솔직히 적으면서도 정확히 어떤 의미인지 파악이 잘 되지 않는다.

하지만 DeviceResources를 살펴보면서 몇가지 느낀 점이 있다.

1. DeviceResources가 하고 있는 작업은 입문자가 따로 처리하기 복잡한 작업들이다.

2. DeviceResources는 따로 제공을 하는 파일이다.

3. DirectX와 관련된 작업들이기는 하나, Rendering과 직접 연관이 있는 것은 아니다. 편의성, 성능과 연관된 작업이다.

 

그렇기에 DeviceResources와 관련된 설명은 아래 링크로 대체한다.

https://github.com/Microsoft/DirectXTK/wiki/DeviceResources

 

microsoft/DirectXTK

The DirectX Tool Kit (aka DirectXTK) is a collection of helper classes for writing DirectX 11.x code in C++ - microsoft/DirectXTK

github.com

결국 DeviceResources도 이전에 살펴보았던 App과 마찬가지로 수정할 수 없는 부분이었다.

정확히는 d3d12.h, DirectXHelper.h와 더 비슷한 것 같다.

더불어 Rendering 부분에서 사용하는 StepTimer.h도 이와 비슷한 부류라 생각한다.

이들의 공통적은 Project 상에서 Common 폴더 밑에 존재한다는 점이다.

 

자세한 것은 살펴봐야 알겠지만 Template Project에서 directory에 따라 기능이 나뉘는 것 같다.

Common/*: Project 전반적으로 사용되는 코드나 함수들. 주로 Microsoft에서 편의성을 위해 제공되는 함수들이 포함됨

Content/*: 실질적으로 Rendering과 관련된 코드들. hlsl등도 여기에 포함 됨.

기타 파일들 : Rendering을 제외한 Windows Application과 관련된 코드. 혹은 이를 Rendering과 연결하는 코드.

 

다음에는 ExampleCreateionMain을 시작으로 Rendering 관련 부분들을 살펴보도록 하겠습니다.

VS 2019에서 제공하는 DirectX12 기본 프로젝트는 다음과 같이 이루어져 있다.

 

이 프로젝트를 빌드 하면 다음과 같은 결과물을 얻을 수 있다.

이 때 전체적인 창을 생성하는 부분이 App.h, App.cpp이다.

// The main function is only used to initialize our IFrameworkView class.
[Platform::MTAThread]
int main(Platform::Array<Platform::String^>^)
{
	auto direct3DApplicationSource = ref new Direct3DApplicationSource();
	CoreApplication::Run(direct3DApplicationSource);
	return 0;
}

App.cpp 파일 안에 선언되어 있는 main 함수다.

그런데 우리가 알던 main 함수와 많이 다르다.

main 함수 앞에 처음 보는 선언도 있고, main 함수가 받는 parameter도, 그 형식도 낯설다.

이는 UWP(Universal Windows Platform)에서 사용하는 C++/CX의 문법으로,

지금 여기서는 자세하게 설명하지 않을 것이다.

 

main 함수안에서는 Direct3DApplicationSource의 reference Object를 생성하고, CoreApplication에서 Run 함수에 parameter로 넘겨주면서 Run 함수를 선언하고 있다.

 

그렇다면 먼저 Direct3DApplicationSource를 보자.

ref class Direct3DApplicationSource sealed : Windows::ApplicationModel::Core::IFrameworkViewSource
{
public:
	virtual Windows::ApplicationModel::Core::IFrameworkView^ CreateView();
};
IFrameworkView^ Direct3DApplicationSource::CreateView()
{
	return ref new App();
}

간단히 설명하면, Direct3DApplicationSource는 Windows::ApplicationModel::Core::IFrameworkViewSource라는 Interface를 Implement 하는 Class이다.

IFrameworkViewSource 안에는 Windows::ApplicationModel::Core::IFrameworkView의 reference를 반환하는 CreateView() 라는 abstract function 하나가 선언되어 있다.

이 코드에서는 CreateView에서 App의 reference object를 생성해 반환한다.

즉, Direct3DApplicationSource는 App의 Factory라는 것이다.

 

그렇다면 이 App을 parameter로 받아서 실행되는 CoreApplication은 무엇일까?

이는 UWP에서 제공되는 Class로, 자세한 내용은 아래 링크로 대체한다.

https://docs.microsoft.com/en-us/uwp/api/windows.applicationmodel.core.coreapplication?view=winrt-19041

 

CoreApplication Class (Windows.ApplicationModel.Core) - Windows UWP applications

Enables apps to handle state changes, manage windows, and integrate with a variety of UI frameworks.

docs.microsoft.com

 

그렇다면 App은 어떤 내용들이 선언되어 있을까?

#include "pch.h"
#include "Common\DeviceResources.h"
#include "ExampleCreationMain.h"

namespace ExampleCreation
{
	// Main entry point for our app. Connects the app with the Windows shell and handles application lifecycle events.
	ref class App sealed : public Windows::ApplicationModel::Core::IFrameworkView
	{
	public:
		App();

		// IFrameworkView methods.
		virtual void Initialize(Windows::ApplicationModel::Core::CoreApplicationView^ applicationView);
		virtual void SetWindow(Windows::UI::Core::CoreWindow^ window);
		virtual void Load(Platform::String^ entryPoint);
		virtual void Run();
		virtual void Uninitialize();

	protected:
		// Application lifecycle event handlers.
		void OnActivated(Windows::ApplicationModel::Core::CoreApplicationView^ applicationView, Windows::ApplicationModel::Activation::IActivatedEventArgs^ args);
		void OnSuspending(Platform::Object^ sender, Windows::ApplicationModel::SuspendingEventArgs^ args);
		void OnResuming(Platform::Object^ sender, Platform::Object^ args);

		// Window event handlers.
		void OnWindowSizeChanged(Windows::UI::Core::CoreWindow^ sender, Windows::UI::Core::WindowSizeChangedEventArgs^ args);
		void OnVisibilityChanged(Windows::UI::Core::CoreWindow^ sender, Windows::UI::Core::VisibilityChangedEventArgs^ args);
		void OnWindowClosed(Windows::UI::Core::CoreWindow^ sender, Windows::UI::Core::CoreWindowEventArgs^ args);

		// DisplayInformation event handlers.
		void OnDpiChanged(Windows::Graphics::Display::DisplayInformation^ sender, Platform::Object^ args);
		void OnOrientationChanged(Windows::Graphics::Display::DisplayInformation^ sender, Platform::Object^ args);
		void OnDisplayContentsInvalidated(Windows::Graphics::Display::DisplayInformation^ sender, Platform::Object^ args);

	private:
		// Private accessor for m_deviceResources, protects against device removed errors.
		std::shared_ptr<DX::DeviceResources> GetDeviceResources();

		std::shared_ptr<DX::DeviceResources> m_deviceResources;
		std::unique_ptr<ExampleCreationMain> m_main;
		bool m_windowClosed;
		bool m_windowVisible;
	};
}

주석에 App의 기능이 대략적으로 설명이 되어 있다.

우리가 실행시킬 어플리케이션을 Windows shell과 연결시키고, lifecycle 이벤트를 조종하는 역할을 한다.

이에 대한 내용은 함수들에 붙은 주석에서도 자세히 설명을 하고 있다.

 

따로 설명할 부분은 Field들이다.

bool로 선언된 두 개의 field들은 밑에서 따로 설명 할 예정이다.

이 외에 우리는 DeviceResources와 ExampleCreationMain를 가지고 있다.

 

DeviceResources는 DirectX device resource들과 관련된 모든 것을 다룬다.

창 크기, dpi부터 해서 d3d관련 변수들의 getter 함수들까지 제공한다.

ExampleCreationMain은 빌드를 했을 시 우리가 실제로 보게 되는 화면.

즉, 무지개빛 큐브가 회전하는 것을 선언하고 있는 부분이다.

 

사실상 이 두 부분이 DirectX를 공부하면서 우리가 머리로도, 손으로도 익혀야 하는 부분이다.

하지만 벌써부터 DirectX 부분으로 빠진다면 나중에 어떤 부분이 공부해야 할 부분이고,

어떤 부분이 넘어갈 수 있는 부분인지 햇갈릴 수 있다.

그러니 DeviceResources와 ExampleCreationMain은 뒤에 다시 언급하고,

우선은 App 함수의 구현부를 보면서 기능을 파악해보도록 한다.

 

App::App() :
	m_windowClosed(false),
	m_windowVisible(true)
{
}

App의 생성자는 m_windowClosed와 m_windowVisible 값을 초기화 해주고 있다.

이 생성자를 생성된 창이 열려 있고 화면 상에 표시가 되게 설정을 해주는 것 같다.

직접 창을 생성한다고 하지 않은 이유는 그 기능을 Initialize에서 해주고 있기 때문이다.

// The first method called when the IFrameworkView is being created.
void App::Initialize(CoreApplicationView^ applicationView)
{
	// Register event handlers for app lifecycle. This example includes Activated, so that we
	// can make the CoreWindow active and start rendering on the window.
	applicationView->Activated +=
		ref new TypedEventHandler<CoreApplicationView^, IActivatedEventArgs^>(this, &App::OnActivated);

	CoreApplication::Suspending +=
		ref new EventHandler<SuspendingEventArgs^>(this, &App::OnSuspending);

	CoreApplication::Resuming +=
		ref new EventHandler<Platform::Object^>(this, &App::OnResuming);
}

이 함수는 IFrameworkView가 생성 될 때 가장 먼저 호출이 된다.

내부적으로는 Application의 LifeCycle과 관련된 Event Handling이 이루어진다.

물론 여기서 사용되는 Event들도 App 안에서 선언을 해두고 있다.

void App::OnActivated(CoreApplicationView^ applicationView, IActivatedEventArgs^ args)
{
	// Run() won't start until the CoreWindow is activated.
	CoreWindow::GetForCurrentThread()->Activate();
}

void App::OnSuspending(Platform::Object^ sender, SuspendingEventArgs^ args)
{
	// Save app state asynchronously after requesting a deferral. Holding a deferral
	// indicates that the application is busy performing suspending operations. Be
	// aware that a deferral may not be held indefinitely. After about five seconds,
	// the app will be forced to exit.
	SuspendingDeferral^ deferral = args->SuspendingOperation->GetDeferral();

	create_task([this, deferral]()
	{
		m_main->OnSuspending();
		deferral->Complete();
	});
}

void App::OnResuming(Platform::Object^ sender, Platform::Object^ args)
{
	// Restore any data or state that was unloaded on suspend. By default, data
	// and state are persisted when resuming from suspend. Note that this event
	// does not occur if the app was previously terminated.

	m_main->OnResuming();
}

이 3가지는 선언, 중지, 재시작 시 호출되는 Event 함수들이다.

OnActivated는 현재 활성화 된 thread의 CoreWindow Instance 활성화 한다.

UWP에서 제공하는 함수로, 화면에 window를 표시 할 때 호출한다.

또한 Run 함수가 동작하기 위해서는 CoreWindow가 반드시 Activate 되어야 하기에, OnActivated에서 선언한다.

https://docs.microsoft.com/en-us/uwp/api/windows.ui.core.corewindow.getforcurrentthread?view=winrt-19041

 

CoreWindow.GetForCurrentThread Method (Windows.UI.Core) - Windows UWP applications

Gets the CoreWindow instance for the currently active thread.

docs.microsoft.com

 

OnSuspending은 app의 상태를 연기 요청이 들어온 이후 비동기적으로 저장한다.

연기 상태가 지속되는 것은 app이 종료 작업을 하기에 너무 바쁜 것을 의미한다.

때문에 5초간 연기 상태가 지속되면, app을 강제로 종료시킨다.

 

OnResuming은 종료 상태로부터 로드 되지 않은 모든 데이터나 상태들을 복구한다.

단, 앱이 종료되는 시점에서는 이 이벤트는 호출되지 않는다.

 

SetWindow 함수는 CoreWindow object가 생성 되었거나, 재생성 되었을 때 호출되는 함수다.

// Called when the CoreWindow object is created (or re-created).
void App::SetWindow(CoreWindow^ window)
{
	window->SizeChanged += 
		ref new TypedEventHandler<CoreWindow^, WindowSizeChangedEventArgs^>(this, &App::OnWindowSizeChanged);

	window->VisibilityChanged +=
		ref new TypedEventHandler<CoreWindow^, VisibilityChangedEventArgs^>(this, &App::OnVisibilityChanged);

	window->Closed += 
		ref new TypedEventHandler<CoreWindow^, CoreWindowEventArgs^>(this, &App::OnWindowClosed);

	DisplayInformation^ currentDisplayInformation = DisplayInformation::GetForCurrentView();

	currentDisplayInformation->DpiChanged +=
		ref new TypedEventHandler<DisplayInformation^, Object^>(this, &App::OnDpiChanged);

	currentDisplayInformation->OrientationChanged +=
		ref new TypedEventHandler<DisplayInformation^, Object^>(this, &App::OnOrientationChanged);

	DisplayInformation::DisplayContentsInvalidated +=
		ref new TypedEventHandler<DisplayInformation^, Object^>(this, &App::OnDisplayContentsInvalidated);
}

이 안에서는 Initialize 함수와 마찬가지로, window의 상태 변화와 관련된 event handling들이 선언되어 있다.

역시 Event들은 App에서 선언하고 있다. 

대부분의 함수들은 눈에 들어올 것이다.

하지만 UWP에서 제공하고 있는 DisplayInformation::GetForCurrentView()은 낯설 것이다.

이 함수는 현재 thread의 CoreApplicationView와 연결되어 있는 DisplayInformation Instance를 반환한다.

이 DisplayInformation Instance는 view와 묶여 있고, 다른 thread에서는 사용할 수 없다.

https://docs.microsoft.com/en-us/uwp/api/windows.graphics.display.displayinformation.getforcurrentview?view=winrt-19041

 

DisplayInformation.GetForCurrentView Method (Windows.Graphics.Display) - Windows UWP applications

Gets the DisplayInformation instance associated with the current thread's CoreApplicationView. This DisplayInformation instance is tied to the view and cannot be used from other threads.

docs.microsoft.com

// Window event handlers.

void App::OnWindowSizeChanged(CoreWindow^ sender, WindowSizeChangedEventArgs^ args)
{
	GetDeviceResources()->SetLogicalSize(Size(sender->Bounds.Width, sender->Bounds.Height));
	m_main->OnWindowSizeChanged();
}

void App::OnVisibilityChanged(CoreWindow^ sender, VisibilityChangedEventArgs^ args)
{
	m_windowVisible = args->Visible;
}

void App::OnWindowClosed(CoreWindow^ sender, CoreWindowEventArgs^ args)
{
	m_windowClosed = true;
}

// DisplayInformation event handlers.

void App::OnDpiChanged(DisplayInformation^ sender, Object^ args)
{
	// Note: The value for LogicalDpi retrieved here may not match the effective DPI of the app
	// if it is being scaled for high resolution devices. Once the DPI is set on DeviceResources,
	// you should always retrieve it using the GetDpi method.
	// See DeviceResources.cpp for more details.
	GetDeviceResources()->SetDpi(sender->LogicalDpi);
	m_main->OnWindowSizeChanged();
}

void App::OnOrientationChanged(DisplayInformation^ sender, Object^ args)
{
	GetDeviceResources()->SetCurrentOrientation(sender->CurrentOrientation);
	m_main->OnWindowSizeChanged();
}

void App::OnDisplayContentsInvalidated(DisplayInformation^ sender, Object^ args)
{
	GetDeviceResources()->ValidateDevice();
}

 이 Event 안의 함수들은 모두 DeviceResources에서 선언된 것들을 사용하고 있다.

그렇기에 지금은 각 Event 함수 이름을 보고 기능을 유추하는 것으로 넘어가면 될 것이다.

 

Load는 Run 함수가 호출 되기 전 외부 리소스들을 불러오거나, 활성화 하는 함수이다.

여기서는 App이 가지고 있는 ExampleCreationMain object인 m_main이 선언되어 있지 않을 때 새 object를 채워넣어준다.

// Initializes scene resources, or loads a previously saved app state.
void App::Load(Platform::String^ entryPoint)
{
	if (m_main == nullptr)
	{
		m_main = std::unique_ptr<ExampleCreationMain>(new ExampleCreationMain());
	}
}

 

대망의 Run 함수다. 이 함수는 window가 활성화 된 이후에 호출이 된다.

// This method is called after the window becomes active.
void App::Run()
{
	while (!m_windowClosed)
	{
		if (m_windowVisible)
		{
			CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);

			auto commandQueue = GetDeviceResources()->GetCommandQueue();
			PIXBeginEvent(commandQueue, 0, L"Update");
			{
				m_main->Update();
			}
			PIXEndEvent(commandQueue);

			PIXBeginEvent(commandQueue, 0, L"Render");
			{
				if (m_main->Render())
				{
					GetDeviceResources()->Present();
				}
			}
			PIXEndEvent(commandQueue);
		}
		else
		{
			CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending);
		}
	}
}

순서대로 살펴보자.

ProcessEvents는 dispatcher에게 input event queue에 있는 event들을 실행시키는 함수이다.

parameter로 받는 CoreProcessEventsOption은 몇 개의 event들을 실행시킬지를 나타낸다.

https://docs.microsoft.com/en-us/uwp/api/windows.ui.core.coredispatcher.processevents?view=winrt-19041

 

CoreDispatcher.ProcessEvents(CoreProcessEventsOption) Method (Windows.UI.Core) - Windows UWP applications

Starts the dispatcher processing the input event queue for this instance of CoreWindow.

docs.microsoft.com

그 아래에 PIXBeginEVent, PIXEndEvent는 PIX라는 라이브러리에서 선언된 함수이다.

PIX event를 이용해 특정 구역의 함수가 CPU에서 작동해야 하는지, 아니면 GPU에서 작동해야 하는지를 표시해주기도 하고, 중요한 작업을 표현하기도 한다.

Windows SDK에서는 오래된 버전인 pix.h를 제공한다. 이는 현재 제공 중인 pix3.h와 비슷한 기능들을 제공하고 있지만, PIX와는 쓸모 없고 호환되지 않는다.

https://devblogs.microsoft.com/pix/winpixeventruntime/

 

WinPixEventRuntime | PIX on Windows

PIX events are used to instrument your game, labeling regions of CPU or GPU work and marking important occurrences.  Including this instrumentation while developing the game can make PIX captures far nicer to work with. An “event” represents a region

devblogs.microsoft.com

마지막으로 App.h 상단부에 include 되어 있는 header들을 살펴보자.

#include "pch.h"
#include "Common\DeviceResources.h"
#include "ExampleCreationMain.h"

Common\DeviceResources.h와 ExampleCreationMain.h는 위에서 한차례 설명 했듯이, 따로 만들어줘야 하는 코드다.

남은 pch.h는 template에서 제공하는 공용 header이다.

#include <wrl.h>
#include <wrl/client.h>
#include <dxgi1_4.h>
#include <d3d12.h>
#include "Common\d3dx12.h"
#include <pix.h>
#include <DirectXColors.h>
#include <DirectXMath.h>
#include <memory>
#include <vector>
#include <agile.h>
#include <concrt.h>

#if defined(_DEBUG)
#include <dxgidebug.h>
#endif

wrl.h와 wrl/client.h는 WRL(Windows Runtime C++ Template Library)의 기능을 사용할 수 있는 header들이다.

이 부분에 대해서는 한 차례 탐구를 했다가 실패를 한 사례가 있는데, 적절한 블로그를 찾아서 링크를 공유한다.

https://megayuchi.com/2016/11/14/winrt-ccx-wrl/

 

WinRT, C++/CX , WRL

먼저 WinRT에 대해서 얘기해보자. Windows 8시절부터 Store App을 개발하던 프로그래머가 아니라면 좀 생소할 수도 있는데 Windows Runtime의 약자이고 Store App을 위한 새로운 Windows API이다. 공식적으로 소��

megayuchi.com

블로그에 정리된 내용을 옮겨오자면

  1. C++로 WinRT API를 사용할 수 있는 통로는 C++/CX와 WRL 두가지가 있다.
  2. WRL은 데스크탑 어플리케이션에서 WinRT API를 호출하거나 MS내부에서 사용할 목적으로 만들었던 것으로 보인다.
  3. MS에선 WRL로의 개발을 장려하고 싶지 않았다. C++로 개발하는 경우 C++/CX를 권장했다.
  4. Windows 8.x가 실패하고 새로운 국면에 접어들었다. 사람들은 여전히 데스크탑 어플리케이션을 원했고 데스크탑 어플리케이션은 Windows에 있어 여전히 중요한 영역이었다.
  5. 새로운 펜.잉크 API, Surface Dial과 같은 디바이스들을 데스크탑에서 사용해야할 필요가 있었고 WRL에 대해서 다시 신경을 쓰게 된것으로 보인다.
  6. 최근 C++/WinRT를 보면 사실상 UWP에서 C++/CX대신 WRL을 사용하게 된다. 만약 MS가 C++/WinRT를 지속적으로 밀게 된다면 사실살 WRL이 C++/CX를 밀어내고 UWP앱 개발도구의 메이저가 된다.

사실 지금은 크게 상관 없을 수 있다.

이 글을 읽는 사람이 있다면, 그 사람도 필시 게임 클라이언트 개발에 관심이 있는 사람일 것이다.

아직 직장을 경험해 보지 않고, 이 분야에 대해서 문외한인 내가 이렇다 저렇다 결론을 내릴 수 있을리가 없다.

다만 우리가 앞에서 대충 넘어갔던 것이 무엇이고, 현재 어떤 상황인지 대략 참고는 가능할 것이라 생각한다.

 

header 안에 "dx"가 있는 것들은 대체로 DirectX 출신이다.

dxgi1_4.h, d3d12.h, dxgidebug.h가 그러하다.

DirectXColors.h, DirectXMath.h도 일단은 DirectX를 사용하기 위해서는 필요하니 동일선상에 둘 수도 있다.

필요에 따라 선택할 수 있지만 DirectX를 사용한다면 이 5가지 header에 DirectXPackedVector.h, DirectXCollision.h는 선언을 해두는 편이 여러모로 편리할 것이다.

 

d3dx12.h MS에서 제공하는 D3D12 Helper Library에 속한 header이다. 

공식은 아니지만 사실상 DirectX는 Windows에서 거의 사용하지 않겠는가?

사용 여부는 실제로 현업에서 사용하는지를 알아보고 판단 할 것 같다.

 

pix.h는 위에서 한차례 설명했듯이 PIX 라이브러리 기능을 사용하는 header이다.

PIX와 호환은 안되지만, Windows SDK에 있어서 간단하게 추가할 수 있으니 그대로 사용하는 것이 편리할 것이다.

 

vector와 memory는 생략하겠다. 이는 C++에서 제공하는 것들이다.

 

agile.h는 MS Document에 검색해도 잘 나오지 않는다.

때문에 본인도 이 부분에 대해서는 확신이 적다. 그러니 참고 링크를 먼저 올리고, 정리를 해보고자 한다.

https://docs.microsoft.com/en-us/cpp/cppcx/platform-agile-class?view=vs-2019

 

Platform::Agile Class

Platform::Agile Class In this article --> Represents an object that has a MashalingBehavior=Standard as an agile object, which greatly reduces the chances for runtime threading exceptions. The Agile enables the non-agile object to call, or be called from,

docs.microsoft.com

https://docs.microsoft.com/en-us/cpp/cppcx/threading-and-marshaling-c-cx?view=vs-2019

 

Threading and Marshaling (C++/CX)

Threading and Marshaling (C++/CX) In this article --> In the vast majority of cases, instances of Windows Runtime classes, like standard C++ objects, can be accessed from any thread. Such classes are referred to as "agile". However, a small number of Windo

docs.microsoft.com

MS에서 agile은 말 그대로 agile한 object를 지칭하는 것 같다.

그 과정에 Threading과 Marshaling이 나오는데, Marshaling은 다음을 참고하자.

https://ko.wikipedia.org/wiki/%EB%A7%88%EC%83%AC%EB%A7%81_(%EC%BB%B4%ED%93%A8%ED%84%B0_%EA%B3%BC%ED%95%99)

 

마샬링 (컴퓨터 과학) - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 컴퓨터 과학에서 마셜링(marshalling, l을 하나만 사용하여 marshaling이라고도 표기)이란 한 객체의 메모리에서 표현방식을 저장 또는 전송에 적합한 다른 데이터 형

ko.wikipedia.org

결국 agile은 Object를 빠르게 공유할 수 있는 것들. 혹은 그 속성을 지칭한다.

Object가 agile하려면 병렬화(threading)이나 직렬화(Marshaling)가 가능해야 하는데,

이것이 불가능한 것들을 가능한 형태로 변경 할 수 있도록 기능을 제공하는 것이 agile.h라고 생각된다.

 

concrt.h는 동시성 런타임과 관련된 header이다.

관련 내용을 찾아보았는데, 이 내용이 코드 내에서 어디에 어떻게 사용되었는지 파악하지 못했다.

우선은 관련 링크를 참조한다. 

https://docs.microsoft.com/ko-kr/cpp/parallel/concrt/overview-of-the-concurrency-runtime?view=vs-2019

 

동시성 런타임 개요

동시성 런타임 개요Overview of the Concurrency Runtime 이 문서의 내용 --> 이 문서에서는 동시성 런타임에 대한 개요를 제공합니다.This document provides an overview of the Concurrency Runtime. 또한 동시성 런타임의

docs.microsoft.com

이번 챕터의 최종 목표는 2가지이다.

1. 예시 Template 코드에서 Rendering 부분만 지워서 해당 부분에 필요한 내용만 채워넣으면 어떤 예시라도 바로바로 테스트가 가능한 형태로 가공하기.

2. Window Desktop Application으로 동일한 기능 구현하기.

 

제대로 시작하기 전에는 1번이 오래 걸리고 2번이 생각보다 쉽게 갈 것 같았다.

하지만 막상 어느정도 정리하고 나니까 1번이 가시권이고 2번이 까마득하다.

그도 그럴게 MS가 만든 Template라서 당연할 수 있지만, 코드 자체가 성능 면을 상당히 세심하게 고려하고 짜여졌다.

때문에 우리처럼 이를 보고 처음 공부하려는 사람들은 어려움이 적지 않다.

하물며 이걸 이해해가면서 다른 방식으로 프로그래밍이라니... 눈물이 앞을 가린다.

 

일단은 1번을 먼저 해놓고, 2번을 고민해보려 한다.

다음에는 앞에서 빼먹었던 ExampleCreationMain, DeviceResources 코드들 중심으로 분석을 하려 한다.

그 뒤에는 책에서의 입문 chapter를 정리하고, 예시 코드를 실제 이 프로젝트에서 구현해보겠다.

내용이 아무것도 없는 이유는 중간에 방향을 잃고 이리저리 방황했기 때문입니다.

 

우선 DirectX12의 예시 코드는 C++/CX를 개발이 된 코드입니다.

 

CX를 찾다 보니 UWP가 나오고, UWP를 찾아보다 보니 Windows RT가 나왔습니다.

 

Windows RT는 Windows 8에 추가가 된 것인데, 예시 코드 내부의 것들이 Windows 10에서 Windows RT에 통합이 된 경우가 있었습니다.

 

지금 약간 CX와 UWP, RT의 구분이 잘 되지 않고 있습니다.

 

그래서 몇시간 동안 CX 보다가 UWP 보다가 RT 보다가 왔다갔다 하는 것 같습니다.

 

그러다가 문뜩 "CX고 RT고 지금 게임 쪽에서 이런걸 쓰고는 있나?"라는 의문도 들기 시작했습니다.

 

클라이언트 팁을 조금 찾아보니까 아직까지는 이런 얘기들과는 거리가 조금 먼 상황인 것 같습니다.

 

그래서 다음주에는 DirectX 코드 분석을 하고, 그 뒤에 Windows Desktop Application Template에서 DirectX12 예시 프로그램과 동일한 기능을 구현해보려 합니다.

 

이래저래 Windows Programming을 병행 해야 하지 않을까 싶은데 시간을 더 내기가 힘들어서 고민이 됩니다.

이 글은 여러차례 분할하여 작성을 할 예정입니다.

프로젝트를 보면서 전혀 이해하지 못하는 문법들이 있었습니다.

 

UWP와 관련하여 계속해서 검색을 하면서도 답을 찾지 못했는데, 한 PPT에서 답을 찾았습니다.

https://www.slideshare.net/dgtman/f1-c-windows-10-uwp

 

프로그래밍 언어의 F1머신 C++을 타고 Windows 10 UWP 앱 개발의 세계로~

Windows 10의 UWP 앱을 개발하면 모든 Windows 10 디바이스에서 앱을 작동할 수 있습니다. 이 UWP 앱을 C++로 개발할 수 있습니다. C++로 앱을 개발하면 크로스 플랫폼 지원의 유리함, 기존 코드의 재활용,

www.slideshare.net

이 PPT에 UWP가 무엇이고, 어떤 장단점과 특징이 있는지 잘 설명이 되어 있습니다.

그중에는 제가 그토록 궁금해 하던 C++/CX라는 것에 대해서도 설명이 되어 있습니다.

 

이를 기반으로 다음에 공부를 하고, 코드를 작성한 뒤 설명을 하려 합니다.

코드는 상세히 분석하여, 지울 수 있는 부분은 지운 상태에서 기본 프로젝트를 생성해볼 계획입니다.

https://docs.microsoft.com/ko-kr/visualstudio/ide/how-to-create-project-templates?view=vs-2019

 

프로젝트 템플릿 만들기 - Visual Studio

방법: 프로젝트 템플릿 만들기How to: Create project templates 이 문서의 내용 --> 이 항목에서는 템플릿을 .zip 파일로 패키징하는 템플릿 내보내기 마법사를 사용하여 템플릿을 만드는 방법을 보여줍니

docs.microsoft.com

이렇게 수정하면ㅁ 이후 더 편하게 DirectX12 예시 코드를 작성할 수 있을것이라 생각합니다.

DirectXMath.h 라이브러리에서 4×4 Matrix를 나타내는 데에는 XMMATRIX라는 클래스가 쓰인다.

struct XMMATRIX;

// Fix-up for (1st) XMMATRIX parameter to pass in-register for ARM64 and vector call; by reference otherwise
#if ( defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || _XM_VECTORCALL_ ) && !defined(_XM_NO_INTRINSICS_)
typedef const XMMATRIX FXMMATRIX;
#else
typedef const XMMATRIX& FXMMATRIX;
#endif

// Fix-up for (2nd+) XMMATRIX parameters to pass by reference
typedef const XMMATRIX& CXMMATRIX;

#ifdef _XM_NO_INTRINSICS_
struct XMMATRIX
#else
__declspec(align(16)) struct XMMATRIX
#endif
{
#ifdef _XM_NO_INTRINSICS_
    union
    {
        XMVECTOR r[4];
        struct
        {
            float _11, _12, _13, _14;
            float _21, _22, _23, _24;
            float _31, _32, _33, _34;
            float _41, _42, _43, _44;
        };
        float m[4][4];
    };
#else
    XMVECTOR r[4];
#endif

    XMMATRIX() = default;

    XMMATRIX(const XMMATRIX&) = default;

#if defined(_MSC_VER) && (_MSC_FULL_VER < 191426431)
    XMMATRIX& operator= (const XMMATRIX& M) noexcept { r[0] = M.r[0]; r[1] = M.r[1]; r[2] = M.r[2]; r[3] = M.r[3]; return *this; }
#else
    XMMATRIX& operator=(const XMMATRIX&) = default;

    XMMATRIX(XMMATRIX&&) = default;
    XMMATRIX& operator=(XMMATRIX&&) = default;
#endif

    constexpr XMMATRIX(FXMVECTOR R0, FXMVECTOR R1, FXMVECTOR R2, CXMVECTOR R3) : r{ R0,R1,R2,R3 } {}
    XMMATRIX(float m00, float m01, float m02, float m03,
             float m10, float m11, float m12, float m13,
             float m20, float m21, float m22, float m23,
             float m30, float m31, float m32, float m33);
    explicit XMMATRIX(_In_reads_(16) const float *pArray);

#ifdef _XM_NO_INTRINSICS_
    float       operator() (size_t Row, size_t Column) const { return m[Row][Column]; }
    float&      operator() (size_t Row, size_t Column) { return m[Row][Column]; }
#endif

    XMMATRIX    operator+ () const { return *this; }
    XMMATRIX    operator- () const;

    XMMATRIX&   XM_CALLCONV     operator+= (FXMMATRIX M);
    XMMATRIX&   XM_CALLCONV     operator-= (FXMMATRIX M);
    XMMATRIX&   XM_CALLCONV     operator*= (FXMMATRIX M);
    XMMATRIX&   operator*= (float S);
    XMMATRIX&   operator/= (float S);

    XMMATRIX    XM_CALLCONV     operator+ (FXMMATRIX M) const;
    XMMATRIX    XM_CALLCONV     operator- (FXMMATRIX M) const;
    XMMATRIX    XM_CALLCONV     operator* (FXMMATRIX M) const;
    XMMATRIX    operator* (float S) const;
    XMMATRIX    operator/ (float S) const;

    friend XMMATRIX     XM_CALLCONV     operator* (float S, FXMMATRIX M);
};

XMMATRIX는 SIMD의 장점을 취하기 위해 4개의 XMVECTOR Instance를 사용한다.

또한 Matrix 곱셈을 위한 연산자와 한 성분을 그 Row와 Column의 색인들을 지정해 접근/수정하기 위한 대괄호 연산자를 중복적재하고 있다.

 

또한 XMMATRIX를 이용해 선언된 FXMMATRIX와 CXMMATRIX가 있다.

이 둘의 설명은 주석을 통해 충분히 이해가 가겠지만, 다시 한번 언급을 해두려 한다.

보통 XMMATRIX를 Parameter로 전달 할 때에는 CXMMATRIX를 사용한다.

하지만 특수한 경우에 FXMMATRIX를 사용해야 하기도 하는데

  • 첫번째 XMMATRIX일 것
  • 위 XMMATRIX 변수 앞에 XMVECTOR가 3개 이상 존재하지 않을 것
  • 위 XMMATRIX 변수 뒤에 2개 이상의 float나 double, XMVECTOR가 존재하지 않을 것

 

생성자의 경우에는 항상 CXMMATRIX만 사용하는 것을 권장한다.

 

XMMATRIX에서는 다양한 생성자 외에, XMMatrixSet이라는 함수로도 Instance 생성이 가능하다.

XMMATRIX    XM_CALLCONV     XMMatrixSet(float m00, float m01, float m02, float m03,
                                        float m10, float m11, float m12, float m13,
                                        float m20, float m21, float m22, float m23,
                                        float m30, float m31, float m32, float m33);

Matrix를 클래스 자료 멤버로 저장할 때에는 Vector를 XMFLOAT2나 XMFLOAT3, XMFLOAT4를 사용한 것과 같이 XMFLOAT4X4나 XMFLOAT3X4, XMFLOAT4X3을 사용하는 것이 권장된다.

// 3x3 Matrix: 32 bit floating point components
struct XMFLOAT3X3
{
    union
    {
        struct
        {
            float _11, _12, _13;
            float _21, _22, _23;
            float _31, _32, _33;
        };
        float m[3][3];
    };

    XMFLOAT3X3() = default;

    XMFLOAT3X3(const XMFLOAT3X3&) = default;
    XMFLOAT3X3& operator=(const XMFLOAT3X3&) = default;

    XMFLOAT3X3(XMFLOAT3X3&&) = default;
    XMFLOAT3X3& operator=(XMFLOAT3X3&&) = default;

    XM_CONSTEXPR XMFLOAT3X3(float m00, float m01, float m02,
                            float m10, float m11, float m12,
                            float m20, float m21, float m22)
        : _11(m00), _12(m01), _13(m02),
          _21(m10), _22(m11), _23(m12),
          _31(m20), _32(m21), _33(m22) {}
    explicit XMFLOAT3X3(_In_reads_(9) const float *pArray);

    float       operator() (size_t Row, size_t Column) const { return m[Row][Column]; }
    float&      operator() (size_t Row, size_t Column) { return m[Row][Column]; }
};

//------------------------------------------------------------------------------
// 4x3 Row-major Matrix: 32 bit floating point components
struct XMFLOAT4X3
{
    union
    {
        struct
        {
            float _11, _12, _13;
            float _21, _22, _23;
            float _31, _32, _33;
            float _41, _42, _43;
        };
        float m[4][3];
        float f[12];
    };

    XMFLOAT4X3() = default;

    XMFLOAT4X3(const XMFLOAT4X3&) = default;
    XMFLOAT4X3& operator=(const XMFLOAT4X3&) = default;

    XMFLOAT4X3(XMFLOAT4X3&&) = default;
    XMFLOAT4X3& operator=(XMFLOAT4X3&&) = default;

    XM_CONSTEXPR XMFLOAT4X3(float m00, float m01, float m02,
                            float m10, float m11, float m12,
                            float m20, float m21, float m22,
                            float m30, float m31, float m32)
        : _11(m00), _12(m01), _13(m02),
          _21(m10), _22(m11), _23(m12),
          _31(m20), _32(m21), _33(m22),
          _41(m30), _42(m31), _43(m32) {}
    explicit XMFLOAT4X3(_In_reads_(12) const float *pArray);

    float       operator() (size_t Row, size_t Column) const { return m[Row][Column]; }
    float&      operator() (size_t Row, size_t Column) { return m[Row][Column]; }
};

// 4x3 Row-major Matrix: 32 bit floating point components aligned on a 16 byte boundary
__declspec(align(16)) struct XMFLOAT4X3A : public XMFLOAT4X3
{
    XMFLOAT4X3A() = default;

    XMFLOAT4X3A(const XMFLOAT4X3A&) = default;
    XMFLOAT4X3A& operator=(const XMFLOAT4X3A&) = default;

    XMFLOAT4X3A(XMFLOAT4X3A&&) = default;
    XMFLOAT4X3A& operator=(XMFLOAT4X3A&&) = default;

    XM_CONSTEXPR XMFLOAT4X3A(float m00, float m01, float m02,
                            float m10, float m11, float m12,
                            float m20, float m21, float m22,
                            float m30, float m31, float m32) :
        XMFLOAT4X3(m00,m01,m02,m10,m11,m12,m20,m21,m22,m30,m31,m32) {}
    explicit XMFLOAT4X3A(_In_reads_(12) const float *pArray) : XMFLOAT4X3(pArray) {}
};

//------------------------------------------------------------------------------
// 3x4 Column-major Matrix: 32 bit floating point components
struct XMFLOAT3X4
{
    union
    {
        struct
        {
            float _11, _12, _13, _14;
            float _21, _22, _23, _24;
            float _31, _32, _33, _34;
        };
        float m[3][4];
        float f[12];
    };

    XMFLOAT3X4() = default;

    XMFLOAT3X4(const XMFLOAT3X4&) = default;
    XMFLOAT3X4& operator=(const XMFLOAT3X4&) = default;

    XMFLOAT3X4(XMFLOAT3X4&&) = default;
    XMFLOAT3X4& operator=(XMFLOAT3X4&&) = default;

    XM_CONSTEXPR XMFLOAT3X4(float m00, float m01, float m02, float m03,
                            float m10, float m11, float m12, float m13,
                            float m20, float m21, float m22, float m23)
        : _11(m00), _12(m01), _13(m02), _14(m03),
          _21(m10), _22(m11), _23(m12), _24(m13),
          _31(m20), _32(m21), _33(m22), _34(m23) {}
    explicit XMFLOAT3X4(_In_reads_(12) const float *pArray);

    float       operator() (size_t Row, size_t Column) const { return m[Row][Column]; }
    float&      operator() (size_t Row, size_t Column) { return m[Row][Column]; }
};

// 3x4 Column-major Matrix: 32 bit floating point components aligned on a 16 byte boundary
__declspec(align(16)) struct XMFLOAT3X4A : public XMFLOAT3X4
{
    XMFLOAT3X4A() = default;

    XMFLOAT3X4A(const XMFLOAT3X4A&) = default;
    XMFLOAT3X4A& operator=(const XMFLOAT3X4A&) = default;

    XMFLOAT3X4A(XMFLOAT3X4A&&) = default;
    XMFLOAT3X4A& operator=(XMFLOAT3X4A&&) = default;

    XM_CONSTEXPR XMFLOAT3X4A(float m00, float m01, float m02, float m03,
                             float m10, float m11, float m12, float m13,
                             float m20, float m21, float m22, float m23) :
        XMFLOAT3X4(m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23) {}
    explicit XMFLOAT3X4A(_In_reads_(12) const float *pArray) : XMFLOAT3X4(pArray) {}
};

//------------------------------------------------------------------------------
// 4x4 Matrix: 32 bit floating point components
struct XMFLOAT4X4
{
    union
    {
        struct
        {
            float _11, _12, _13, _14;
            float _21, _22, _23, _24;
            float _31, _32, _33, _34;
            float _41, _42, _43, _44;
        };
        float m[4][4];
    };

    XMFLOAT4X4() = default;

    XMFLOAT4X4(const XMFLOAT4X4&) = default;
    XMFLOAT4X4& operator=(const XMFLOAT4X4&) = default;

    XMFLOAT4X4(XMFLOAT4X4&&) = default;
    XMFLOAT4X4& operator=(XMFLOAT4X4&&) = default;

    XM_CONSTEXPR XMFLOAT4X4(float m00, float m01, float m02, float m03,
                            float m10, float m11, float m12, float m13,
                            float m20, float m21, float m22, float m23,
                            float m30, float m31, float m32, float m33)
        : _11(m00), _12(m01), _13(m02), _14(m03),
          _21(m10), _22(m11), _23(m12), _24(m13),
          _31(m20), _32(m21), _33(m22), _34(m23),
          _41(m30), _42(m31), _43(m32), _44(m33) {}
    explicit XMFLOAT4X4(_In_reads_(16) const float *pArray);

    float       operator() (size_t Row, size_t Column) const { return m[Row][Column]; }
    float&      operator() (size_t Row, size_t Column) { return m[Row][Column]; }
};

// 4x4 Matrix: 32 bit floating point components aligned on a 16 byte boundary
__declspec(align(16)) struct XMFLOAT4X4A : public XMFLOAT4X4
{
    XMFLOAT4X4A() = default;

    XMFLOAT4X4A(const XMFLOAT4X4A&) = default;
    XMFLOAT4X4A& operator=(const XMFLOAT4X4A&) = default;

    XMFLOAT4X4A(XMFLOAT4X4A&&) = default;
    XMFLOAT4X4A& operator=(XMFLOAT4X4A&&) = default;

    XM_CONSTEXPR XMFLOAT4X4A(float m00, float m01, float m02, float m03,
                             float m10, float m11, float m12, float m13,
                             float m20, float m21, float m22, float m23,
                             float m30, float m31, float m32, float m33)
        : XMFLOAT4X4(m00,m01,m02,m03,m10,m11,m12,m13,m20,m21,m22,m23,m30,m31,m32,m33) {}
    explicit XMFLOAT4X4A(_In_reads_(16) const float *pArray) : XMFLOAT4X4(pArray) {}
};

 

또한 Matrix와 관련된 여러 기능들에 대한 함수도 지원을 한다.

bool        XM_CALLCONV     XMMatrixIsNaN(FXMMATRIX M);
bool        XM_CALLCONV     XMMatrixIsInfinite(FXMMATRIX M);
bool        XM_CALLCONV     XMMatrixIsIdentity(FXMMATRIX M);

XMMATRIX    XM_CALLCONV     XMMatrixMultiply(FXMMATRIX M1, CXMMATRIX M2);
XMMATRIX    XM_CALLCONV     XMMatrixMultiplyTranspose(FXMMATRIX M1, CXMMATRIX M2);
XMMATRIX    XM_CALLCONV     XMMatrixTranspose(FXMMATRIX M);
XMMATRIX    XM_CALLCONV     XMMatrixInverse(_Out_opt_ XMVECTOR* pDeterminant, _In_ FXMMATRIX M);
XMVECTOR    XM_CALLCONV     XMMatrixDeterminant(FXMMATRIX M);
_Success_(return)
bool        XM_CALLCONV     XMMatrixDecompose(_Out_ XMVECTOR *outScale, _Out_ XMVECTOR *outRotQuat, _Out_ XMVECTOR *outTrans, _In_ FXMMATRIX M);

XMMATRIX    XM_CALLCONV     XMMatrixIdentity();

물론 이런 함수에는 Transformation 함수도 포함되어 있다.

XMMATRIX    XM_CALLCONV     XMMatrixTranslation(float OffsetX, float OffsetY, float OffsetZ);
XMMATRIX    XM_CALLCONV     XMMatrixTranslationFromVector(FXMVECTOR Offset);
XMMATRIX    XM_CALLCONV     XMMatrixScaling(float ScaleX, float ScaleY, float ScaleZ);
XMMATRIX    XM_CALLCONV     XMMatrixScalingFromVector(FXMVECTOR Scale);
XMMATRIX    XM_CALLCONV     XMMatrixRotationX(float Angle);
XMMATRIX    XM_CALLCONV     XMMatrixRotationY(float Angle);
XMMATRIX    XM_CALLCONV     XMMatrixRotationZ(float Angle);
XMMATRIX    XM_CALLCONV     XMMatrixRotationRollPitchYaw(float Pitch, float Yaw, float Roll);
XMMATRIX    XM_CALLCONV     XMMatrixRotationRollPitchYawFromVector(FXMVECTOR Angles);
XMMATRIX    XM_CALLCONV     XMMatrixRotationNormal(FXMVECTOR NormalAxis, float Angle);
XMMATRIX    XM_CALLCONV     XMMatrixRotationAxis(FXMVECTOR Axis, float Angle);
XMMATRIX    XM_CALLCONV     XMMatrixRotationQuaternion(FXMVECTOR Quaternion);
XMMATRIX    XM_CALLCONV     XMMatrixTransformation2D(FXMVECTOR ScalingOrigin, float ScalingOrientation, FXMVECTOR Scaling,
                                                     FXMVECTOR RotationOrigin, float Rotation, GXMVECTOR Translation);
XMMATRIX    XM_CALLCONV     XMMatrixTransformation(FXMVECTOR ScalingOrigin, FXMVECTOR ScalingOrientationQuaternion, FXMVECTOR Scaling,
                                                   GXMVECTOR RotationOrigin, HXMVECTOR RotationQuaternion, HXMVECTOR Translation);
XMMATRIX    XM_CALLCONV     XMMatrixAffineTransformation2D(FXMVECTOR Scaling, FXMVECTOR RotationOrigin, float Rotation, FXMVECTOR Translation);
XMMATRIX    XM_CALLCONV     XMMatrixAffineTransformation(FXMVECTOR Scaling, FXMVECTOR RotationOrigin, FXMVECTOR RotationQuaternion, GXMVECTOR Translation);

 

DirectX 9, 10의 D3DX Library에, 11에서는 XNA Math에서 3차원 그래픽에 필요한 것들을 지원한다.

코드상에서는 xnamath.h를 include 하면 사용이 가능하다고 설명되어 있다.

하지만 현재 예시 DirectX12 코드에서는 관련 기능들이 DiretXMath.h에 선언이 되어 있다.

명확하게 이전 되었다는 내용을 찾지는 못했지만, xnamath.h와 DirectXMath.h의 document를 보면 설명이 동일하다.

때문에 여기서는 DirectXMath.h를 기준으로 설명하겠다.

또한 관련해서 답이 충분히 될법한 링크 또한 첨부한다.

https://docs.microsoft.com/en-us/windows/win32/dxmath/pg-xnamath-migration#header-changes

 

Code Migration from the XNA Math Library - Win32 apps

Code Migration from the XNA Math Library In this article --> This overview describes the changes required to migrate existing code using the XNA Math library to the DirectXMath library. The DirectXMath library uses a new set of headers. Replace the xnamath

docs.microsoft.com

 

이 Library는 벡터 연산에 매우 유용하다.

이를 설명하기 위해서는 먼저 Library의 연산 과정을 조금 언급해야 한다.

DirectXMath Library는 Windows와 XBox 360에서 사용가능한 특별한 하드웨어 레지스터들을 활용한다.

Windows에서는 SSE2(Streaming SIMD Extensions) 명령집합을 사용하는데,
SIMD의 명령덜은 128비트 너비의 SIMD(Single Instruction Multiple Data) 레지스터들을 이용해
한 명령에서 32비트 float나 int 4개를 동시에 처리가 가능하다.

 

DirectXMath에서 핵심 Vector 형식은 SIMD 하드웨어 레지스터들에 대응되는 XMVECTOR이다.

#if defined(_XM_SSE_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_)
typedef __m128 XMVECTOR;
#elif defined(_XM_ARM_NEON_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_)
typedef float32x4_t XMVECTOR;
#else
typedef __vector4 XMVECTOR;
#endif

128bit 크기로, 하나의 SIMD 명령으로 처리되는 4개의 32bit float 값들로 이루어져있다.

저기서 _XM_SSE_INTRINSICS_는 SSE나 SSE2를, _XM_ARM_NEON_INTRINSICS_는 Windows RT.

즉, 모바일 플랫폼을 나타낸다.

_XM_NO_INTRINSICS_는 Windows 환경이 아닌 곳에서 DirectXMath가 따로 사용되는 경우를 일컫는다.

 

여기서 __m128은 특별한 SIMD 형식이다.

이 형식의 Vector들은 계산 시 반드시 SIMD의 장점을 취하게 된다.

게다가 2, 3차원 Vector일 경우, 사용하지 않는 element를 0으로 설정해 동일하게 SIMD의 장점을 취한다.

 

다만 여기에는 몇가지 규칙이 있는데, 플랫폼마다 규칙이 다르다.

특히 Windows 23bit와 64bit, XBox 360의 규칙들이 서로 다르다.

이런 차이로부터 독립적이기 위해, XMVECTOR 대신 CXXMVECTOR 형식과 FXMVECTOR형식을 사용한다.

// Fix-up for (1st-3rd) XMVECTOR parameters that are pass-in-register for x86, ARM, ARM64, and vector call; by reference otherwise
#if ( defined(_M_IX86) || defined(_M_ARM) || defined(_M_ARM64) || _XM_VECTORCALL_ ) && !defined(_XM_NO_INTRINSICS_)
typedef const XMVECTOR FXMVECTOR;
#else
typedef const XMVECTOR& FXMVECTOR;
#endif

// Fix-up for (4th) XMVECTOR parameter to pass in-register for ARM, ARM64, and x64 vector call; by reference otherwise
#if ( defined(_M_ARM) || defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || (_XM_VECTORCALL_ && !defined(_M_IX86) ) ) && !defined(_XM_NO_INTRINSICS_)
typedef const XMVECTOR GXMVECTOR;
#else
typedef const XMVECTOR& GXMVECTOR;
#endif

// Fix-up for (5th & 6th) XMVECTOR parameter to pass in-register for ARM64 and vector call; by reference otherwise
#if ( defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || _XM_VECTORCALL_ ) && !defined(_XM_NO_INTRINSICS_)
typedef const XMVECTOR HXMVECTOR;
#else
typedef const XMVECTOR& HXMVECTOR;
#endif

// Fix-up for (7th+) XMVECTOR parameters to pass by reference
typedef const XMVECTOR& CXMVECTOR;

 

32bit와 64bit의 차이는 복사 전달과 참조전달이다.

FXMVECTOR, CXMVECTOR. 더 나아가 GXMVECTOR, HXMVECTOR의 차이는 다음과 같다.

매개변수로 XMVECTOR을 전달 할 때,

  1. 1, 2, 3번째 XMVECTOR는 FXMVECTOR로 전달해야 한다.
  2. 4번째 XMVECTOR는 GXMVECTOR로 전달해야 한다.
  3. 5, 6번째 XMVECTOR는 HXMVECTOR로 전달해야 한다.
  4. 7번째 이후로는 CXMVECTOR로 전달해야 한다.

단, 생성자에서는 __vectorcall의 제한으로 인해, 규칙이 더 간단해진다.

  1. 처음 3개의 XMVECTOR는 FXMVECTOR로 전달한다.
  2. 그 이후에는 모두 CXMVECTOR로 전달한다.

함수에 전달 할 때 XMVECTOR가 연속적이지 않아도 상관 없다.

XMVECTOR들만 순번을 매겨 위 형식을 적용한다.

이와 관련된 문서를 첨부한다.

https://docs.microsoft.com/en-us/windows/win32/dxmath/pg-xnamath-internals

 

Library Internals - Win32 apps

Library Internals In this article --> This topic describes the internal design of the DirectXMath library. Calling Conventions To enhance portability and optimize data layout, you need to use the appropriate calling conventions for each platform supported

docs.microsoft.com

 

XMVECTOR는 16Byte 경계에 정렬되어야 하는데, Local/Global Value에서는 자동으로 정렬된다.

Field의 경우, XMVECTOR 대신 XMFLOAT2, XMFLOAT3, XMFLOAT4를 사용하는 것이 바람직하다.

// 2D Vector; 32 bit floating point components
struct XMFLOAT2
{
    float x;
    float y;

    XMFLOAT2() = default;

    XMFLOAT2(const XMFLOAT2&) = default;
    XMFLOAT2& operator=(const XMFLOAT2&) = default;

    XMFLOAT2(XMFLOAT2&&) = default;
    XMFLOAT2& operator=(XMFLOAT2&&) = default;

    XM_CONSTEXPR XMFLOAT2(float _x, float _y) : x(_x), y(_y) {}
    explicit XMFLOAT2(_In_reads_(2) const float *pArray) : x(pArray[0]), y(pArray[1]) {}
};
// 3D Vector; 32 bit floating point componentsstruct XMFLOAT3
{
    float x;
    float y;
    float z;

    XMFLOAT3() = default;

    XMFLOAT3(const XMFLOAT3&) = default;
    XMFLOAT3& operator=(const XMFLOAT3&) = default;

    XMFLOAT3(XMFLOAT3&&) = default;
    XMFLOAT3& operator=(XMFLOAT3&&) = default;

    XM_CONSTEXPR XMFLOAT3(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {}
    explicit XMFLOAT3(_In_reads_(3) const float *pArray) : x(pArray[0]), y(pArray[1]), z(pArray[2]) {}
};
// 4D Vector; 32 bit floating point components
struct XMFLOAT4
{
    float x;
    float y;
    float z;
    float w;

    XMFLOAT4() = default;

    XMFLOAT4(const XMFLOAT4&) = default;
    XMFLOAT4& operator=(const XMFLOAT4&) = default;

    XMFLOAT4(XMFLOAT4&&) = default;
    XMFLOAT4& operator=(XMFLOAT4&&) = default;

    XM_CONSTEXPR XMFLOAT4(float _x, float _y, float _z, float _w) : x(_x), y(_y), z(_z), w(_w) {}
    explicit XMFLOAT4(_In_reads_(4) const float *pArray) : x(pArray[0]), y(pArray[1]), z(pArray[2]), w(pArray[3]) {}
};

하지만 이 형식들을 계산에 직접 사용하면 SIMD의 장점을 취하지 못한다.

SIMD를 활용하려면 이 형식의 Instance를 XMVECTOR 형식으로 변환해야 한다.

이를 위해 DirectXMath에서는 다양한 적재 함수들을 제공한다.

XMVECTOR    XM_CALLCONV     XMLoadInt2(_In_reads_(2) const uint32_t* pSource);
XMVECTOR    XM_CALLCONV     XMLoadInt2A(_In_reads_(2) const uint32_t* PSource);
XMVECTOR    XM_CALLCONV     XMLoadFloat2(_In_ const XMFLOAT2* pSource);
XMVECTOR    XM_CALLCONV     XMLoadFloat2A(_In_ const XMFLOAT2A* pSource);
XMVECTOR    XM_CALLCONV     XMLoadSInt2(_In_ const XMINT2* pSource);
XMVECTOR    XM_CALLCONV     XMLoadUInt2(_In_ const XMUINT2* pSource);

XMVECTOR    XM_CALLCONV     XMLoadInt3(_In_reads_(3) const uint32_t* pSource);
XMVECTOR    XM_CALLCONV     XMLoadInt3A(_In_reads_(3) const uint32_t* pSource);
XMVECTOR    XM_CALLCONV     XMLoadFloat3(_In_ const XMFLOAT3* pSource);
XMVECTOR    XM_CALLCONV     XMLoadFloat3A(_In_ const XMFLOAT3A* pSource);
XMVECTOR    XM_CALLCONV     XMLoadSInt3(_In_ const XMINT3* pSource);
XMVECTOR    XM_CALLCONV     XMLoadUInt3(_In_ const XMUINT3* pSource);

XMVECTOR    XM_CALLCONV     XMLoadInt4(_In_reads_(4) const uint32_t* pSource);
XMVECTOR    XM_CALLCONV     XMLoadInt4A(_In_reads_(4) const uint32_t* pSource);
XMVECTOR    XM_CALLCONV     XMLoadFloat4(_In_ const XMFLOAT4* pSource);
XMVECTOR    XM_CALLCONV     XMLoadFloat4A(_In_ const XMFLOAT4A* pSource);
XMVECTOR    XM_CALLCONV     XMLoadSInt4(_In_ const XMINT4* pSource);
XMVECTOR    XM_CALLCONV     XMLoadUInt4(_In_ const XMUINT4* pSource);

또한, 반대로 XMVECTOR Instance를 XMFLOAT* 형식으로 변환하는 저장 함수도 제공한다.

void        XM_CALLCONV     XMStoreInt2(_Out_writes_(2) uint32_t* pDestination, _In_ FXMVECTOR V);
void        XM_CALLCONV     XMStoreInt2A(_Out_writes_(2) uint32_t* pDestination, _In_ FXMVECTOR V);
void        XM_CALLCONV     XMStoreFloat2(_Out_ XMFLOAT2* pDestination, _In_ FXMVECTOR V);
void        XM_CALLCONV     XMStoreFloat2A(_Out_ XMFLOAT2A* pDestination, _In_ FXMVECTOR V);
void        XM_CALLCONV     XMStoreSInt2(_Out_ XMINT2* pDestination, _In_ FXMVECTOR V);
void        XM_CALLCONV     XMStoreUInt2(_Out_ XMUINT2* pDestination, _In_ FXMVECTOR V);

void        XM_CALLCONV     XMStoreInt3(_Out_writes_(3) uint32_t* pDestination, _In_ FXMVECTOR V);
void        XM_CALLCONV     XMStoreInt3A(_Out_writes_(3) uint32_t* pDestination, _In_ FXMVECTOR V);
void        XM_CALLCONV     XMStoreFloat3(_Out_ XMFLOAT3* pDestination, _In_ FXMVECTOR V);
void        XM_CALLCONV     XMStoreFloat3A(_Out_ XMFLOAT3A* pDestination, _In_ FXMVECTOR V);
void        XM_CALLCONV     XMStoreSInt3(_Out_ XMINT3* pDestination, _In_ FXMVECTOR V);
void        XM_CALLCONV     XMStoreUInt3(_Out_ XMUINT3* pDestination, _In_ FXMVECTOR V);

void        XM_CALLCONV     XMStoreInt4(_Out_writes_(4) uint32_t* pDestination, _In_ FXMVECTOR V);
void        XM_CALLCONV     XMStoreInt4A(_Out_writes_(4) uint32_t* pDestination, _In_ FXMVECTOR V);
void        XM_CALLCONV     XMStoreFloat4(_Out_ XMFLOAT4* pDestination, _In_ FXMVECTOR V);
void        XM_CALLCONV     XMStoreFloat4A(_Out_ XMFLOAT4A* pDestination, _In_ FXMVECTOR V);
void        XM_CALLCONV     XMStoreSInt4(_Out_ XMINT4* pDestination, _In_ FXMVECTOR V);
void        XM_CALLCONV     XMStoreUInt4(_Out_ XMUINT4* pDestination, _In_ FXMVECTOR V);

또한 XMVECTOR Object의 내용을 설정하는 용도의 함수들을 정의하고 있다.

//(0, 0, 0, 0)
XMVECTOR    XM_CALLCONV     XMVectorZero();

// (1, 1, 1, 1)
XMVECTOR    XM_CALLCONV     XMVectorSplatOne();

//(x, y, z, w)
XMVECTOR    XM_CALLCONV     XMVectorSet(float x, float y, float z, float w);

//(value, value, value, value)
XMVECTOR    XM_CALLCONV     XMVectorReplicate(float Value);

//(Vx, Vx, Vx, Vx)
XMVECTOR    XM_CALLCONV     XMVectorSplatX(FXMVECTOR V);

//(Vy, Vy, Vy, Vy)
XMVECTOR    XM_CALLCONV     XMVectorSplatY(FXMVECTOR V);

//(Vz, Vz, Vz, Vz)
XMVECTOR    XM_CALLCONV     XMVectorSplatZ(FXMVECTOR V);

//(Vw, Vw, Vw, Vw)
XMVECTOR    XM_CALLCONV     XMVectorSplatW(FXMVECTOR V);

이 외에 XMVECTOR Instance의 한 성분만 읽거나, 변경하는 조회, 설정 함수들도 제공한다.

float       XM_CALLCONV     XMVectorGetX(FXMVECTOR V);
float       XM_CALLCONV     XMVectorGetY(FXMVECTOR V);
float       XM_CALLCONV     XMVectorGetZ(FXMVECTOR V);
float       XM_CALLCONV     XMVectorGetW(FXMVECTOR V);

XMVECTOR    XM_CALLCONV     XMVectorSetX(FXMVECTOR V, float x);
XMVECTOR    XM_CALLCONV     XMVectorSetY(FXMVECTOR V, float y);
XMVECTOR    XM_CALLCONV     XMVectorSetZ(FXMVECTOR V, float z);
XMVECTOR    XM_CALLCONV     XMVectorSetW(FXMVECTOR V, float w);

 

Const XMVECTOR Instance에는 반드시 XMVECTORF32 형식을 사용해야 한다.

간단히 말해, 초기화 구문을 사용하고자 할 때에는 항상 XMVECTORF32를 사용해야 한다는 것이다.

XMVECTORF32는 16Byte 경계로 정렬된 Structure로, XMVECTOR로의 변호나 연산자를 지원한다.

// Conversion types for constants
__declspec(align(16)) struct XMVECTORF32
{
    union
    {
        float f[4];
        XMVECTOR v;
    };

    inline operator XMVECTOR() const { return v; }
    inline operator const float*() const { return f; }
#if !defined(_XM_NO_INTRINSICS_) && defined(_XM_SSE_INTRINSICS_)
    inline operator __m128i() const { return _mm_castps_si128(v); }
    inline operator __m128d() const { return _mm_castps_pd(v); }
#endif
};

또한 XMVECTORU32를 이용해 정수 자료를 담은 상수 XMVECTOR를 생성하는 것도 가능하다.

__declspec(align(16)) struct XMVECTORU32
{
    union
    {
        uint32_t u[4];
        XMVECTOR v;
    };

    inline operator XMVECTOR() const { return v; }
#if !defined(_XM_NO_INTRINSICS_) && defined(_XM_SSE_INTRINSICS_)
    inline operator __m128i() const { return _mm_castps_si128(v); }
    inline operator __m128d() const { return _mm_castps_pd(v); }
#endif
};
XMGLOBALCONST XMVECTORU32 g_XMMaskX                 = { { { 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000 } } };
XMGLOBALCONST XMVECTORU32 g_XMMaskY                 = { { { 0x00000000, 0xFFFFFFFF, 0x00000000, 0x00000000 } } };
XMGLOBALCONST XMVECTORU32 g_XMMaskZ                 = { { { 0x00000000, 0x00000000, 0xFFFFFFFF, 0x00000000 } } };
XMGLOBALCONST XMVECTORU32 g_XMMaskW                 = { { { 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF } } };

XMVECTOR에는 Vector의 덧셈, 뺄셈, 스칼라 곱셈을 위한 Operator Overloading이 지원된다.

보통은 직관적이라 활성화 하지만, 일부 응용 프로그램은 성능상의 이유로 이를 비활성화 한다.

Operator Overloading을 비활성화 하고 싶으면 macro 상수 XM_NO_OPERATOR_OVERLOADS를 정의해야 한다.

// Vector operators

#ifndef _XM_NO_XMVECTOR_OVERLOADS_
XMVECTOR    XM_CALLCONV     operator+ (FXMVECTOR V);
XMVECTOR    XM_CALLCONV     operator- (FXMVECTOR V);

XMVECTOR&   XM_CALLCONV     operator+= (XMVECTOR& V1, FXMVECTOR V2);
XMVECTOR&   XM_CALLCONV     operator-= (XMVECTOR& V1, FXMVECTOR V2);
XMVECTOR&   XM_CALLCONV     operator*= (XMVECTOR& V1, FXMVECTOR V2);
XMVECTOR&   XM_CALLCONV     operator/= (XMVECTOR& V1, FXMVECTOR V2);

XMVECTOR&   operator*= (XMVECTOR& V, float S);
XMVECTOR&   operator/= (XMVECTOR& V, float S);

XMVECTOR    XM_CALLCONV     operator+ (FXMVECTOR V1, FXMVECTOR V2);
XMVECTOR    XM_CALLCONV     operator- (FXMVECTOR V1, FXMVECTOR V2);
XMVECTOR    XM_CALLCONV     operator* (FXMVECTOR V1, FXMVECTOR V2);
XMVECTOR    XM_CALLCONV     operator/ (FXMVECTOR V1, FXMVECTOR V2);
XMVECTOR    XM_CALLCONV     operator* (FXMVECTOR V, float S);
XMVECTOR    XM_CALLCONV     operator* (float S, FXMVECTOR V);
XMVECTOR    XM_CALLCONV     operator/ (FXMVECTOR V, float S);
#endif /* !_XM_NO_XMVECTOR_OVERLOADS_ */

수학과 관련된 Library답게, DirectXMath Library에서도 다양한 공식에서 사용되는 상수의 근사값을 지원한다.

XM_CONST float XM_PI        = 3.141592654f;
XM_CONST float XM_2PI       = 6.283185307f;
XM_CONST float XM_1DIVPI    = 0.318309886f;
XM_CONST float XM_1DIV2PI   = 0.159154943f;
XM_CONST float XM_PIDIV2    = 1.570796327f;
XM_CONST float XM_PIDIV4    = 0.785398163f;

또한 radian과 degree를 변환하는 함수들도 제공한다.

// Unit conversion

inline XM_CONSTEXPR float XMConvertToRadians(float fDegrees) { return fDegrees * (XM_PI / 180.0f); }
inline XM_CONSTEXPR float XMConvertToDegrees(float fRadians) { return fRadians * (180.0f / XM_PI); }

그리고 최솟값, 최댓값을 위한 매크로 함수들도 정의한다.

#if defined(__XNAMATH_H__) && defined(XMMin)
#undef XMMin
#undef XMMax
#endif

template<class T> inline T XMMin(T a, T b) { return (a < b) ? a : b; }
template<class T> inline T XMMax(T a, T b) { return (a > b) ? a : b; }

 

XMVECTOR가 Operator Overloading으로만 연산을 하는 것은 아니다.

XMVECTOR 연산에 필요한 여러 함수들을 따로 지원하기도 한다.

/****************************************************************************
 *
 * 3D vector operations
 *
 ****************************************************************************/

bool        XM_CALLCONV     XMVector3Equal(FXMVECTOR V1, FXMVECTOR V2);
uint32_t    XM_CALLCONV     XMVector3EqualR(FXMVECTOR V1, FXMVECTOR V2);
bool        XM_CALLCONV     XMVector3EqualInt(FXMVECTOR V1, FXMVECTOR V2);
uint32_t    XM_CALLCONV     XMVector3EqualIntR(FXMVECTOR V1, FXMVECTOR V2);
bool        XM_CALLCONV     XMVector3NearEqual(FXMVECTOR V1, FXMVECTOR V2, FXMVECTOR Epsilon);
bool        XM_CALLCONV     XMVector3NotEqual(FXMVECTOR V1, FXMVECTOR V2);
bool        XM_CALLCONV     XMVector3NotEqualInt(FXMVECTOR V1, FXMVECTOR V2);
bool        XM_CALLCONV     XMVector3Greater(FXMVECTOR V1, FXMVECTOR V2);
uint32_t    XM_CALLCONV     XMVector3GreaterR(FXMVECTOR V1, FXMVECTOR V2);
bool        XM_CALLCONV     XMVector3GreaterOrEqual(FXMVECTOR V1, FXMVECTOR V2);
uint32_t    XM_CALLCONV     XMVector3GreaterOrEqualR(FXMVECTOR V1, FXMVECTOR V2);
bool        XM_CALLCONV     XMVector3Less(FXMVECTOR V1, FXMVECTOR V2);
bool        XM_CALLCONV     XMVector3LessOrEqual(FXMVECTOR V1, FXMVECTOR V2);
bool        XM_CALLCONV     XMVector3InBounds(FXMVECTOR V, FXMVECTOR Bounds);

bool        XM_CALLCONV     XMVector3IsNaN(FXMVECTOR V);
bool        XM_CALLCONV     XMVector3IsInfinite(FXMVECTOR V);

XMVECTOR    XM_CALLCONV     XMVector3Dot(FXMVECTOR V1, FXMVECTOR V2);
XMVECTOR    XM_CALLCONV     XMVector3Cross(FXMVECTOR V1, FXMVECTOR V2);
XMVECTOR    XM_CALLCONV     XMVector3LengthSq(FXMVECTOR V);
XMVECTOR    XM_CALLCONV     XMVector3ReciprocalLengthEst(FXMVECTOR V);
XMVECTOR    XM_CALLCONV     XMVector3ReciprocalLength(FXMVECTOR V);
XMVECTOR    XM_CALLCONV     XMVector3LengthEst(FXMVECTOR V);
XMVECTOR    XM_CALLCONV     XMVector3Length(FXMVECTOR V);
XMVECTOR    XM_CALLCONV     XMVector3NormalizeEst(FXMVECTOR V);
XMVECTOR    XM_CALLCONV     XMVector3Normalize(FXMVECTOR V);
XMVECTOR    XM_CALLCONV     XMVector3ClampLength(FXMVECTOR V, float LengthMin, float LengthMax);
XMVECTOR    XM_CALLCONV     XMVector3ClampLengthV(FXMVECTOR V, FXMVECTOR LengthMin, FXMVECTOR LengthMax);
XMVECTOR    XM_CALLCONV     XMVector3Reflect(FXMVECTOR Incident, FXMVECTOR Normal);
XMVECTOR    XM_CALLCONV     XMVector3Refract(FXMVECTOR Incident, FXMVECTOR Normal, float RefractionIndex);
XMVECTOR    XM_CALLCONV     XMVector3RefractV(FXMVECTOR Incident, FXMVECTOR Normal, FXMVECTOR RefractionIndex);
XMVECTOR    XM_CALLCONV     XMVector3Orthogonal(FXMVECTOR V);
XMVECTOR    XM_CALLCONV     XMVector3AngleBetweenNormalsEst(FXMVECTOR N1, FXMVECTOR N2);
XMVECTOR    XM_CALLCONV     XMVector3AngleBetweenNormals(FXMVECTOR N1, FXMVECTOR N2);
XMVECTOR    XM_CALLCONV     XMVector3AngleBetweenVectors(FXMVECTOR V1, FXMVECTOR V2);
XMVECTOR    XM_CALLCONV     XMVector3LinePointDistance(FXMVECTOR LinePoint1, FXMVECTOR LinePoint2, FXMVECTOR Point);
void        XM_CALLCONV     XMVector3ComponentsFromNormal(_Out_ XMVECTOR* pParallel, _Out_ XMVECTOR* pPerpendicular, _In_ FXMVECTOR V, _In_ FXMVECTOR Normal);
XMVECTOR    XM_CALLCONV     XMVector3Rotate(FXMVECTOR V, FXMVECTOR RotationQuaternion);
XMVECTOR    XM_CALLCONV     XMVector3InverseRotate(FXMVECTOR V, FXMVECTOR RotationQuaternion);
XMVECTOR    XM_CALLCONV     XMVector3Transform(FXMVECTOR V, FXMMATRIX M);
XMFLOAT4*   XM_CALLCONV     XMVector3TransformStream(_Out_writes_bytes_(sizeof(XMFLOAT4)+OutputStride*(VectorCount-1)) XMFLOAT4* pOutputStream,
                                                     _In_ size_t OutputStride,
                                                     _In_reads_bytes_(sizeof(XMFLOAT3)+InputStride*(VectorCount-1)) const XMFLOAT3* pInputStream,
                                                     _In_ size_t InputStride, _In_ size_t VectorCount, _In_ FXMMATRIX M);
XMVECTOR    XM_CALLCONV     XMVector3TransformCoord(FXMVECTOR V, FXMMATRIX M);
XMFLOAT3*   XM_CALLCONV     XMVector3TransformCoordStream(_Out_writes_bytes_(sizeof(XMFLOAT3)+OutputStride*(VectorCount-1)) XMFLOAT3* pOutputStream,
                                                          _In_ size_t OutputStride,
                                                          _In_reads_bytes_(sizeof(XMFLOAT3)+InputStride*(VectorCount-1)) const XMFLOAT3* pInputStream,
                                                          _In_ size_t InputStride, _In_ size_t VectorCount, _In_ FXMMATRIX M);
XMVECTOR    XM_CALLCONV     XMVector3TransformNormal(FXMVECTOR V, FXMMATRIX M);
XMFLOAT3*   XM_CALLCONV     XMVector3TransformNormalStream(_Out_writes_bytes_(sizeof(XMFLOAT3)+OutputStride*(VectorCount-1)) XMFLOAT3* pOutputStream,
                                                           _In_ size_t OutputStride,
                                                           _In_reads_bytes_(sizeof(XMFLOAT3)+InputStride*(VectorCount-1)) const XMFLOAT3* pInputStream,
                                                           _In_ size_t InputStride, _In_ size_t VectorCount, _In_ FXMMATRIX M);
XMVECTOR    XM_CALLCONV     XMVector3Project(FXMVECTOR V, float ViewportX, float ViewportY, float ViewportWidth, float ViewportHeight, float ViewportMinZ, float ViewportMaxZ,
                                             FXMMATRIX Projection, CXMMATRIX View, CXMMATRIX World);
XMFLOAT3*   XM_CALLCONV     XMVector3ProjectStream(_Out_writes_bytes_(sizeof(XMFLOAT3)+OutputStride*(VectorCount-1)) XMFLOAT3* pOutputStream,
                                                   _In_ size_t OutputStride,
                                                   _In_reads_bytes_(sizeof(XMFLOAT3)+InputStride*(VectorCount-1)) const XMFLOAT3* pInputStream,
                                                   _In_ size_t InputStride, _In_ size_t VectorCount,
                                                   _In_ float ViewportX, _In_ float ViewportY, _In_ float ViewportWidth, _In_ float ViewportHeight, _In_ float ViewportMinZ, _In_ float ViewportMaxZ,
                                                   _In_ FXMMATRIX Projection, _In_ CXMMATRIX View, _In_ CXMMATRIX World);
XMVECTOR    XM_CALLCONV     XMVector3Unproject(FXMVECTOR V, float ViewportX, float ViewportY, float ViewportWidth, float ViewportHeight, float ViewportMinZ, float ViewportMaxZ,
                                               FXMMATRIX Projection, CXMMATRIX View, CXMMATRIX World);
XMFLOAT3*   XM_CALLCONV     XMVector3UnprojectStream(_Out_writes_bytes_(sizeof(XMFLOAT3)+OutputStride*(VectorCount-1)) XMFLOAT3* pOutputStream,
                                                     _In_ size_t OutputStride,
                                                     _In_reads_bytes_(sizeof(XMFLOAT3)+InputStride*(VectorCount-1)) const XMFLOAT3* pInputStream,
                                                     _In_ size_t InputStride, _In_ size_t VectorCount,
                                                     _In_ float ViewportX, _In_ float ViewportY, _In_ float ViewportWidth, _In_ float ViewportHeight, _In_ float ViewportMinZ, _In_ float ViewportMaxZ,
                                                     _In_ FXMMATRIX Projection, _In_ CXMMATRIX View, _In_ CXMMATRIX World);

-----------------------------

본 블로그는 [DirectX11을 이용한 3D 게임 프로그래밍 입문]을 기반으로 공부를 하며,
DirectX12와 다른 부분을 수정해가며 정리를 하는 글입니다.

 

한글 자료가 필요하시다면, 부족하게나마 도움이 되었으면 합니다.

만약 더 자세한 내용이 필요하시다면, 공식 레퍼런스를 보시는 것을 추천합니다.

https://docs.microsoft.com/en-us/windows/win32/dxmath/ovw-xnamath-reference

 

DirectXMath programming reference - Win32 apps

DirectXMath programming reference In this article --> This section contains reference material for the DirectXMath Library. In this section DirectXMath DirectXMath programming guide -->

docs.microsoft.com

 

Linear Transform(선형 변환)

어떤 Vector에 대한 함수 중 다음 조건을 만족하고, 오직 그럴 때, 그 함수를 Linear Transform이라 부른다.

Linear Transform일 때에는 다음 또한 만족한다.

Standard Basis Vector(표준기저벡터)

현재 Coordinate System의 Axis들과 같은 방향인 Unit Vector.

Standard Basis Vector를 이용하면 일반 Vector도 다음과 같이 나타낼 수 있다.

이를 Linear Transform하면 그 Linearity가 성립하여 다음이 성립한다.

이는 이전에 한번 언급했던 Linear Combination이다.

따라서 하나의 Vector와 Matrix의 곱셈으로 표기할 수 있다.

이러한 Matrix ALinear Transform τMatrix Expression이라 부른다.

 

Scaling(비례)

물체의 크기를 바꾸는 효과를 내며, 선형변환이다.

Scaling Transform은 다음과 같이 정의된다.

Matrix Expression은 다음과 같다.

이 Matrix를 Scaling Matrix 라고 부른다.

 

Rotation(회전)

Vector v를 Axis n에 대해서 Θ 각도만큼 회전시키는 Transform이 있다.

이에 대한 Matrix Expression은 다음과 같다.

Rotation Matrix에는 흥미로운 속성이 있다.

Rotation Matrix의 각 Row Vector는 Unit size이고, Row Vector들은 서로 Orthogonal하다. 

따라서 Rotation Matrix는 Orthogonal Matrix이다.

 

Orthogonal Matrix는 Inverse Matrix이 자신의 Transposed Matrix와 같다는 속성이 있다.

이로 인해 Rotation Matrix의 Inverse Matrix는 다음과 같이 정의될 수 있다.

일반적으로 Inverse Matrix을 쉽고 효율적으로 계산할 수 있는 Orthogonal Matrix를 다루는 것이 바람직하다.

특히 Rotation Axis가 Standard Basis Vector인 경우에는 Rotation Matrix가 매우 간단해진다.

다음은 차례대로 x축, y축, z축에 대한 Rotation Matrix이다.

Homogeneous Coordinate(동차좌표)

3차원 Vector에 w 성분을 추가한 4-Element의 형태를 나타낸다.

추가된 성분 w는 서술하는 것이 Dot인지 Vector인지에 따라 그 값이 달라진다.

Vector인 경우에는 w 값은 0이, Dot인 경우에는 1이 된다.

나중에 보면 알겠지만, Dot에 대해서 w = 1인 경우, 정확한 이동이 가능하다. 

반대로 Vector에 대해서 w = 0인 경우, 이동 시 Vector가 변하지 않는다.

 

Rigid Body Transformation(강체 변환)

Transform 시 물체의 형태가 그대로 유지되는 Transformation.

 

Affine Transformation(아핀변환)

Linear Transformation에 Translation이 결합된 것.

Lintear Transformation으로 서술하지 못하는 Transformation을 서술하기 위해 사용된다.

이를 Matrix로 나타내면 아래와 같다.

여기에 w = 1인 HomogeneousCoordinate를 도입하면 아래와 같이 더 간결하게 표기할 수 있다.

위 식에서 사용된 4×4 Matrix를 Affine Transform의 Matrix Expression이라 부른다.

여기서 추가된 b는 하나의 Translation을 나타낸다는 점을 주목하자.

Vector에는 위치가 없으므로, 이 이동은 적용되지 말아야 한다.

하지만 Affine Transformation의 Linear Transformation 부분은 여전히 벡터에 적용되어야 한다.

Vector의 w = 0으로 설정함으로써, b에 의한 Translation은 적용되지 않는다.

 

그렇다면 Affine Transformation의 기하학적 의미는 무엇일까?

이는 Affine Transform을 Row Vector로 표시해보면 조금 더 쉽게 알아차릴 수 있다.

τ는 Rotation Transformation이므로 길이와 각도가 보존된다.

구체적으로, τ는 Standard Basis Vector i, j, k만 새로운 방향 τ(i), τ(j), τ(k)로회전한다.

Vector b는 단지 원점으로부터의 변위를 나타내는 Position Vector일 뿐이다.

이는 책상 위에 가지런히 올려져 있는 큐브를 손으로 집어 올린 모양을 생각하면 편하다.

또한 이런 원리는 Scaling이나 Skew(기울이기)에도 동일하게 적용된다.

그저 Standard Basis Vector가 왜곡되어 있을 뿐이다.

 

Identity Transformation(항등변환)

주어진 인수를 그대로 돌려주는 Linear Transformation.

이 Transformation의 Matrix Expression은 Unit Matrix이다.

이러한 맥락에서, Translation Transformation을 Affine Transformation으로 정의할 수 있다.

이를 Translation Matrix라고 부른다.

 

Change of Coordinate Transformation(좌표 변경 변환)

한 Coordinate System의 Coordinate를 다른 Coordinate System의 Coordinate로 Transform 하는 것을 말한다.

여기서 강조하고 싶은 것은 기하구조가 아니라 Coordinate System이 바뀐다는 점이다.

 

또한 Change of Coordinate Transformation은 결합 법칙이 성립된다.

이는 작게 보여도 생각보다 큰 성능 향상으로 이어지는 경우가 적지 않다.

 

Coordinate System F, G, H가 있다고 가정하자.

Change of Coordinate Transformation A는 Coordinate System F에서 G로, B는 G에서 H로 Transform 하는 Transformation이다.

이 때, C=AB일 때, C가 가지는 의미는 Coordinate System F에서 H로 Transform 하는 Transformation이다.

반대로 C의 Inverse Matrix는 Coordinate System H에서 F로 Transform 하는 Transformation이다.

마찬가지로 Inverse Matrix A와 B 역시 각각 G에서 F로, H에서 G로 Transform 하는 Transformation을 뜻한다.

 

여기서 다루는 Coordinate System은 모두 Reversible하다.

즉, 모든 Change of Coordinate Transformation Matrix는 Inverse Matrix가 존재한다.

 

또한 앞서 언급했던 모든 Rigid Body Transformation과 Change of Coordinate Transformation은 모두 동치이다.

생각해보면 당연하다. Change of Coordinate Transformation은 Coordinate System의 위치와 방향이 다 다르다.

때문에 한 Coordinate system에서 다른 Coordinate System으로의 Transformation에는 Coordinate들의 Rotation과 Translation이 필요하다.

이런 부분을 구해보면 결국 동일한 형태의 공식에 도달한다.

둘의 차이는 Transformation을 해석하는 방법의 차이 뿐이다.

 

----------------------------

 

이로써 책에서 언급한 이론적인 부분에 대한 정리는 모두 마쳤습니다.

다음에는 지난 2~3주간 설명한 부분들에 대한 DX12에서의 관련 함수들을 정리하고,
본격적인 DirectX12 Programming 입문과 함께 기본 코드의 분석을 병행하겠습니다.

Matrix(행렬)

Row(행)과 Column(열)로 이루어진 실수들의 사각 배열.

Row와 Column의 갯수에 따라 m×n Matrix라고 부른다.

이 때, m×n은 Matrix의 Dimension(차원)이라 부른다.

 

Square Matrix(정방 행렬)

Row와 Column의 수가 같은 Matrix.

Inverse Matrix(역행렬)이 존재한다.

 

Entry(성분)

Matrix를 구성하는 각각의 수들을 지칭. Element(원소)라고도 한다.

Matrix의 Entry를 지칭 할 때는 Matrix 이름에 아래첨자로 Column과 Row의 번호를 지정한다.

 

Row Vector(행 백터)
Matrix의 Column이 하나인 Matrix.

Vector라 부르는 이유는 이런 Matrix는 주로 Vector를 표현할 때 사용되기 때문이다.

반대로 Row가 하나인 Matrix는 Column Vector(열 벡터)라고 부른다.

이러한 Row/Column Vector를 지칭할 때는 아래첨자 하나만 사용한다.

 

간혹 Matrix의 Column이나 Row를 Vector로 간주하는 경우가 있다.

Matrix A를 Row Vector들로 표현한 것

이 때 각 Row Vector나 Column Vector를 지칭 할 때는 Column 번호나 Row 번호를 지칭 할 수 없다.

때문에 이 때는 '*'를 적어준다.

 

Matrix의 연산

기본적으로 Matrix간의 연산은 두 Matrix의 Column과 Row 수가 동일해야 한다.

Matrix간의 연산(=, +, -)는 같은 Element끼리 연산하기 때문이다.

Matrix와 Scalar간의 연산은 모든 Element에 동일하게 적용된다.

 

Matrix에 적용되는 연산법칙

  • 덧셈의 교환법칙
  • 덧셈의 결합법칙
  • Matrix에 대한 스칼라 값의 분배법칙
  • 스칼라들에 대한 Matrix의 분배법칙

 

Matrix간의 곱셈

두 Matrix A(m × n)와 B(n × p)가 있을 때, 이 두 Matrix의 곱은 AB로 정의된다.

Matrix AB를 C라고 정의 했을 때, C는 m × p Matrix이고,

이의 ij번째 성분은 Matrix A의 i번째 Row Vector와 B의 j번째 Column Vector의 Inner Production을 한 결과이다.

따라서 Matrix들 간에 곱 연산이 성립하기 위해서는 Matrix A의 Row 수와 Matrix B의 Column 수가 일치해야 한다.

다른 말로, A의 Row Vector의 Dimension과 B의 Column Vector의 Dimension이 일치해야 한다.

이 둘이 일치하지 않으면 두 Vector간의 Inner Production이 성립하지 않는다.

 

다음 Vector와 Matrix의 곱 연산을 보자.

uA가 1 × 3 Row Vector로 평가됨을 주목하며 다음 연산을 보자.

따라서

이 관계가 성립한다.

이는 Linear Combination(선형 결합)의 한 예로,

Vector·Matrix 연산인 uA가 Matrix A의 Row들의 Linear Combination에

Vector u에서 비롯된 Scalar x, y, z가 적용된 것임을 말해준다.

이는 어떤 1 × n Row Vector u와 n × m Matrix A 에해 항상 성립한다.

이 외에 Matrix간의 곱셈에서는 배분법칙결합법칙이 만족한다.

때문에 Matrix 곱 연산을 할 때에는 순서를 적절히 선택할 수 있다.

Transpose(전치)

Transpose Matrix는 주어진 Matrix의 행과 열을 맞바꾼 것을 말한다.

Transpose Matrix는 Matrix 이름에 윗첨자로 T를 붙인다.

Identity Matrix(단위행렬)

Main Diagonal Element(좌상에서 우하로의 주된 대각선에 있는 성분)이 모두 1이고,

나머지 Element들은 0인 Square Matrix.

Identity Matrix는 그 이름에서 알 수 있듯이 Matrix간의 곱셈에서 Identy Element(항등원) 역할을 한다.

때문에 Square Matrix와 Identy Matrix간의 곱셈은 교환법칙이 성립한다.

 

Determinant(행렬식)

Square Matrix를 받아서 실수 값을 산출하는 특별한 함수.

Square Matrix A의 determinant는 det A로 표기한다.

 

기하학적으로 2-Dimension Matrix의 Determinant는 넓이를, 3 Dimension Matrix의 Determinant는 부피를 나타낸다.

이를 이용해 Linear Transform 연산 하에 부피가 변하는 방식에 대한 정보를 제공함을 증명하는 것이 가능하다.

이에 대해서는 자세히 작성 된 링크로 대체한다.

https://twlab.tistory.com/44

 

[Linear Algebra] Lecture 20-(2) 행렬식(Determinant)의 기하학적 해석(Geometrical Analysis)

이번 강의는 행렬식(Determinant)에 관한 마지막 강의다. 이번에 알아볼 내용은 determinant가 기하학적(geometrical)으로 어떤 의미를 갖는지에 대해서 알아볼 것이다. 미리 결론부터 언급하자면 행렬식(d

twlab.tistory.com

또한 Determinant는 Cramer's rule(크라메의 법칙)을 이용해 1차 연립방정식을 푸는 데에도 사용된다.

Cramer's rule와 이를 이용한 1차 연립방정식을 푸는 방법 또한 링크로 대체한다.

https://twlab.tistory.com/43?category=668741

 

[Linear Algebra] Lecture 20-(1) 행렬식(Determinant)과 역행렬(Inverse Matrix), 그리고 크래머 공식(Cramer's Rule)

이번 시간에 다룰 내용은 행렬식(Determinant)과 역행렬(Inverse Matrix)의 관계, 그리고 크래머 공식(Cramer's Rule)에 관한 내용이다. 지난 Lecture 18, Lecture 19에 이어 행렬식을 다루는 세 번째 강의다. 앞..

twlab.tistory.com

http://www.mesacc.edu/~scotz47781/mat150/notes/cramers_rule/Cramers_Rule_3_by_3_Notes.pdf

하지만 우리가 지금 Determinant를 보는 이유는 Inver Matrix를 구할 때 쓰이기 때문이다.

 

Determinant를 이용하면 다음 명제를 증명하는 것도 가능하다.

Square MAtrix A는 오직 detA ≠ 0일 때에만 Invertible 하다.

이 명제를 이용하면 주어진 Matrix가 Invertible 한지 쉽게 알 수 있다.

 

Matrix Minor(부분행렬, 소행렬)

Determinant를 설명하기 위해서는 Matrix Minor가 요구되므로 짚고 넘어가겠다.

n × n Matrix A가 주어졌을 때, 특정 번호의 Row와 Column을 삭제한 (n - 1) × (n - 1) Matrix를 말한다.

Determinant의 Definition

Determinant는 재귀적으로 정의된다.

예를 들어 4 × 4 Matrix의 Determinant는 3 × 3 Matrix의 Determinant를 항으로 하여 정의된다.

3 × 3 Matrix의 Determinant는 2 × 2 Matrix의 Determinant로,

2 × 2 Matrix의 Determinant는 1 × 1 Matrix의 Determinant로 정의된다.

(1 × 1 Matrix의 Determinant는 그 Element의 값과 동일하다.)

n × n Matrix의 Determinant는 다음과 같이 정의된다.

Adjoint Matrix(딸림행렬, 수반행렬)

이를 설명하기 위해서는 몇가지 선행 정의가 필요하다.

n × n Matrix A에 대해, 각 Element들은 Cofactor(여인수)가 존재한다.

이 때 Matrix A의 각 Element들을 Cofactor로 대체 해서 만든 Matrix를 A의 Cofactor Matrix(여인수행렬)이라 부른다.

Adjoint Matrix는 이 Cofactor Matrix의 Tranpose Matrix이다.

뒤에 나오지만, Adjoint Matrix를 이용하면 Inverse Matrix를 구하는 공식을 구할 수 있다.

 

Inverse Matrix(역행렬)

Matrix간에는 나눗셈 연산은 없지만, Inverse에 대한 정의는 존재한다.

  1. 오직 Square Matrix만이 Inverse Matrix를 가진다.
    때문에 Inverse Matrix를 논의 할 때는 항상 Square Matrix라는 가정이 깔려 있다.
  2. n × n Matrix M의 Inverse Matrix 역시 n × n 이며, Matrix 이름에 윗첨자로 -1을 적어서 표기한다.
  3. 모든 Square Matrix에 Inverse Matrix가 존재하는 것은 아니다.
    Inverse Matrix가 존재하는 Matrix를 가르켜 Invertible Matrix(가역행렬)이라 부른다.
    반대로 Inverse Matrix가 존재하지 않으면 Singular Matrix(특이행렬)이라 부른다.
  4. Inverse Matrix가 존재할 경우, 그 Inverse Matrix는 고유하다.
  5. 어떤 Matrix M과 그의 Inverse Matrix를 곱하면 Identity Matrix가 나온다.
    이 연산은 교환법칙이 성립한다.

Inverse Matrix는 Matrix Equation을 풀 때 유용하다.

Inverse Matrix는 Adjoint Matrix와 Determinant로 구할 수 있다.

마지막으로 Matrix 곱 연산의 Inverse 에 대한 유용한 대수적 속성을 하나 소개하겠다.

 

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

05. DirectXMath.h에서의 Vector 관련 기능  (0) 2020.05.22
04. 기초 필수 수학 - Transform  (0) 2020.05.22
02. 기초 필수 수학 - Vector  (0) 2020.05.08
01. DirectX 12 Project Setting  (0) 2020.05.08
00. 글쓰기에 앞서  (0) 2020.05.08

+ Recent posts