https://www.acmicpc.net/problem/10253

 

10253번: 헨리

문제 이제 10 살이 된 헨리(Henry)는 수학에 소질이 있다. 수학선생님인 아메스(Ahmes)는 오늘 헨리에게 분수에 대해 가르쳐줬고, 헨리는 분수를 이리저리 계산해보는 것이 너무 재미있었다. 그러던

www.acmicpc.net

a / b 분수가 입력 되었을 때 x(i) = {x | a/b >= 1 / x 중 가장 큰 x}일 때 가장 마지막 x(i)를 출력하는 문제입니다.

 

처음에는 gcd와 lcm으로 연산을 하였으나, 시간이 너무 오래 걸렸습니다.

 

그 다음에 연산한 것은 a와 b의 각 차수 별 항에서 규칙성을 찾아서 적용하는 것입니다

min(i) = b(i) / a(i) + 1일 때, a(i + 1) = a(i) * min(i) - b(i), b(i + 1) = b(i) * min(i)를 적용하였습니다.

하지만 이 역시 시간 초과가 걸렸습니다.

 

솔직히 이보다 더 빠르게 만들 자신이 없었습니다.

입력을 c스타일 입력으로 바꾸기도 했으나, 여전히 문제가 해결되지 않았습니다.

 

그러다가 정답을 찾아 보는데, 어이 없는 문제점이 두개가 있었습니다.

 

하나는 반복문을 do-while이 아니라 그냥 while문으로 하는 것.

이렇게 되면 처음부터 분자가 1일 때는 자기 자신이 출력이 되는데 왜 성립하는지 몰랐습니다.

하지만 곧, 제가 문제를 잘못 읽었다는 것을 깨달았습니다.

이 문제는 자기 자신이 답이 될 수 있었습니다.

이 부분이 조금 뼈아팠습니다.

 

다른 하나의 문제는 연산이 다 끝나면 항상 gcd로 나누어 주는 것.

서로소를 곱하고 더하는데 최대공약수가 1보다 큰 경우가 발생을 하는가 싶었습니다.

이를 확인하기 위해 값을 최대로 올려보았더니, 곧바로 확인이 가능했습니다.

1에 근접하고 분모가 매우 큰 경우에는 종종 최대공약수가 1보다 큰 경우가 발생하기도 하였습니다.

 

문제의 해결법은 곧잘 찾아내지만, 아직은 세세하게 런타임을 줄이지는 못하는 것 같습니다.

매일 한문제씩 풀어나가면서 이런 실수를 최대한 줄이고, 문제 푸는 속도도 키우도록 하겠습니다.

더보기
#include <iostream>

using namespace std;

int GCD(const int& a, const int& b)
{
    return (b == 0) ? a : GCD(b, a % b);
}

int main()
{
    int testcase = 0, a = 0, b = 0, min = 0, gcd = 0;
    
    cin >> testcase;

    while (testcase-- > 0)
    {
        cin >> a >> b;        
        while (a != 1)
        {
            min = (b + a - 1) / a;
            a = a * min - b;
            b *= min;
            gcd = GCD(a, b);
            a /= gcd;
            b /= gcd;
        } 
        cout << b << endl;
    }

    return 0;
}

 

#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를 이용한 방법은 여유가 생길 때 하도록 보류한다.

괄호가 지워진 길이 50 이하의 덧셈과 뺄셈만 존재하는 식의 최소값을 구하는 문제입니다.

숫자는 99999 이하이며, 0부터 시작 할 수 있습니다.

식은 숫자로 시작하고 숫자로 끝나며, 연산자가 두개 이상 연속으로 입력되지 않습니다.

 

최소가 나오는 방법은 간단합니다.

- 연산자를 기점으로 괄호를 쳐서 그 안의 연산을 우선적으로 해주면 됩니다.

 

구현 방법으로는 입력되는 식을 -로 split 해주고, 각 substring을 다시 +로 split한 뒤 연산을 해줍니다.

그리고 그 결과물을 다시 - 연산을 해주면 됩니다.

 

이 간단한 문제를 저는 처음 시작하는 substring에도

+ 연산이 포함될 수 있다는 것을 간과하여 3번이나 실패를 했습니다.

정답율도 50% 언저리였는데 참 슬펐습니다.

 

더보기
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <algorithm>

using namespace std;

const int StringToInt(const string& str)
{
    int ret = 0;
    for (unsigned int i = 0; i < str.size(); ++i)
    {
        ret *= 10;
        ret += str[i] - '0';
    }
    return ret;
}

const long long StringToList(const string& list)
{
    long long ret = 0;
    string temp;
    vector<string> pParse;
    stringstream ss(list);
    
    while (getline(ss, temp, '+'))
    {
        pParse.push_back(temp);
    }
    for (const auto& iter : pParse)
    {
        ret += StringToInt(iter);
    }
    return ret;
}

int main()
{
    long long ret = 0;
    string input, temp;
    vector<string> mParse;
    vector<int> mParseNum;

    cin >> input;
    stringstream ss(input);
    while (getline(ss, temp, '-'))
    {
        mParse.push_back(temp);
    }
    ret = StringToList(mParse[0]);
    for (unsigned int i = 1; i < mParse.size(); ++i)
    {
        ret -= StringToList(mParse[i]);
    }
    cout << ret << endl;

    return 0;
}

https://www.acmicpc.net/problem/6064

 

6064번: 카잉 달력

문제 최근에 ICPC 탐사대는 남아메리카의 잉카 제국이 놀라운 문명을 지닌 카잉 제국을 토대로 하여 세워졌다는 사실을 발견했다. 카잉 제국의 백성들은 특이한 달력을 사용한 것으로 알려져 있�

www.acmicpc.net

문제가 워낙 쉬워 보여 하나 더 풀어보았습니다.

S = {<x:y> | x = S % M, y = S % N}라고 했을 때 S를 구하라는 문제입니다.

간단히 두 Modular 연산 결과를 보고 원래 수를 구하라는 문제인데,

간단하게 하나씩 더해보았는데 정답을 맞출 수 있었습니다. 

 

단, x와 y중 하나를 기준으로 M과 N의 최소공배수 이상 더했음에도 답이 나오지 않으면

나올 수 없는 결과 값이므로 -1을 출력합니다.

사실 귀찮아서 최소공배수가 아니라 M * N까지 연산을 하였는데 아슬아슬하게 통과할 수 있었습니다.

 

이 문제를 언제 왜 못풀었나 살펴보니 4년 전이었습니다.

아마 대충 도전 했다가 놀았거나,

다국어로 되어 있는 걸로 보아 영어로 적힌 것을 해석하지 못하고 포기했던 것 같습니다.

 

더보기
#include <iostream>

using namespace std;

int main()
{
    int testcase = 0, M = 0, N = 0, x = 0, y = 0;
    cin >> testcase;
    while (testcase-- > 0)
    {
        int sum = 0;
        cin >> M >> N >> x >> y;
        while ((x != y) && (sum < (M * N)))
        {
            if (x > y)
            {
                y += N;
                sum += N;
            }
            else
            {
                x += M;
            }
        }
        if (sum >= (M * N))
        {
            cout << -1 << endl;
        }
        else
        {
            cout << x << endl;
        }
    }
}

 

https://www.acmicpc.net/problem/10989

 

10989번: 수 정렬하기 3

첫째 줄에 수의 개수 N(1 ≤ N ≤ 10,000,000)이 주어진다. 둘째 줄부터 N개의 줄에는 숫자가 주어진다. 이 수는 10,000보다 작거나 같은 자연수이다.

www.acmicpc.net

문제는 맞췄으나, 역시 혼자서는 맞추지를 못했습니다.

 

우선 어제 말한대로, 새로운 vector 선언을 최소화 한 Merge Sort를 구현하였습니다.

두 개의 subvector를 merge 할 때만 임시로 vector를 선언하였는데, 그럼에도 메모리가 부족하였습니다.

 

이 이상은 어떻게 줄일 방법이 없다고 판단하여 검색을 해보았습니다.

하지만 뜻밖에도, 문제는 다른 곳에 있었습니다.

 

대체로 다들 메모리와 시간 문제에 직면하는데,

다들 메모리 문제는 counting sort(입력값을 index로 하는 array에 갯수 저장)로 해결했습니다.

하지만 시간 문제는 알고리즘이 아니라 c스타일 array, c스타일 입출력.

그리고 입력의 최대값을 저장하여 출력 시 순회 횟수를 줄이는 것으로 시간을 줄였습니다.

 

대략적으로 시간복잡도를 계산하다보니 이런 부분은 신경쓰지 못한 것 같습니다.

다음에는 이런 부분까지도 항상 염두를 해두겠습니다.

 

더보기
#include <iostream>
#include <cstdio>

using namespace std;

int main()
{
    int size = 0, temp = 0, max = 0;
    int arr[10000] = { 0 };
    
    cin >> size;
    for (int i = 0; i < size; ++i)
    {
        scanf("%d", &temp);
        max = temp > max ? temp : max;
        ++arr[temp - 1];
    }

    for (int i = 0; i < max; ++i)
    {
        if (arr[i] > 0)
        {
            for (int j = 0; j < arr[i]; ++j)
            {
                printf("%d\n", i + 1);
            }
        }
    }

    return 0;
}

youtu.be/a8x3YHP0Rtk

다행히 오늘 안에 Teleport로 유의미한 결과를 이끌어낼 수 있었습니다.

다만, 항상 되는 것은 아니었습니다.

어떨 때에는 Teleport를 먼저 하고, 어떨 때에는 PlayMontage를 먼저 하는 것 같습니다.

 

또한 보는 것과 같이 너무 위쪽으로 Teleport 하는 것도 문제라고 생각합니다.

우선 RopeExitTop부터 Teleport 수치를 조정하고, 항상 Teleport가 발생한 뒤 Montage가 재생되도록 하려 합니다.

 

그 뒤에는 RopeExitBottom이 정삭작동 하도록 하고, 그 뒤에는 RopeEnterBottom.

마지막으로 RopeEnterTop이 작동되도록 하려 합니다.

 

만약 이 부분이 해결 된다면, Wall과 Ladder는 그대로 수정하면 될 것입니다.

다만 몇가지 문제점도 있었습니다.

 

먼저, Jump로 Exit 할 때 Climb와의 범위에서 벗어나면 Walk Animation이 재생되지 않는 경우가 발생했습니다.

또한 꼭대기에서 Climb Exit 한 이후에도 같은 현상이 발생했습니다.

마지막으로 정면으로 이동하면서 Jump 하는 도중에

Climb 하면 Climb Move Animation이 재생되지 않는 경우도 발생했습니다.

 

이런 버그적인 요소 외에, Animation이 재생되게 하기 위해 불필요한 부분도 구현된 점이 있습니다. 

하지만 우선은 기능 구현을 먼저 하고, 더 나은 코드로 수정을 할 계획입니다.

 

 

'개발일지 > Treasure Hunter' 카테고리의 다른 글

20.07.01 개발일지  (0) 2020.07.01
20.06.29 개발일지  (0) 2020.06.29
20.06.25 개발일지  (0) 2020.06.25
20.06.22 개발일지  (0) 2020.06.22
20.06.20 개발일지  (0) 2020.06.20

아직 오늘 개발이 끝난 것은 아니지만

더 이상 유의미한 진행 사항이 오늘 안에 나오지 않을것이라 판단하여 미리 일지를 적습니다.

 

Montage에 여차여차하여 Client에서만 Log가 출력되게 조정을 해보았습니다.

또한 Montage 하나하나에 조건을 달아 ExitClimb Montage가 재생되는지도 확인을 해보았습니다.

그 결과 ExitClimb가 호출이 되기는 하였으나, 재생은 이동 입력에 따라 여러 차례 발생하기도 했습니다.

그리고 그 횟수 만큼 Montage 재생 종료 이벤트가 발생했습니다.

 

제대로 작동하지 않는 이유는 Montage 재생 이벤트가 여러번 호출되어 반복적으로 Montage 앞부분이 재생 되다가, 

첫 Montage 종료 이벤트로 인해 Climb 상태가 해제가 되는 것으로 예상됩니다.

이에 대해 DisableInput을 해보았으나, 유의미한 결과를 얻지는 못했습니다.

 

두번째로, Montage가 재생될 때 Character를 Teleport 해보았습니다.

그런데 Montage가 여러번 재생되는 만큼, Teleport도 여러번 발생했습니다.

그리고 이 때마다 Character가 진동을 하는 것을 확인했습니다.

정면으로 100만큼 이동시킨다면 3번 발생 시 300을 이동하는 것이 아니라 0과 100을 3번 왕복합니다.

그리고 Character가 이동해 있기도 하고, 떨어지기도 합니다.

 

그나마 다행인 점은, 이 덕분에 Montage가 제대로 재생되고, 작동을 할 수 있다는 희망을 보기는 했습니다.

 

그러다가 Climb System 구현 영상을 하나 찾아 보았습니다.

https://www.youtube.com/watch?v=BKiSTM-G9pQ

길이가 길고, BP 위주로 하느라 정확히 파악은 못했으나, 

Notify를 통해 Event를 발생해서 진행하는 것 같습니다.

 

이에 영감을 받고 새로운 구조를 한번 시도해볼까 합니다.

그래서 현재 branch와 새로운 branch를 두고 왔다갔다 하면서 진행하고자 합니다.

 

마지막으로 한가지 버그를 발견했습니다.

Climb 상태에서 Exit 할 때 떨어지면서 이동을 하여 Climb로부터 완전히 벗어나면,

Animation이 정상 재생되지 않습니다.

 

 

요즘 이 프로젝트에 얼마나 시간을 할애해야 할지 고민이 생겼습니다.

Unreal 하나만을 바라보고 가기에는 방향을 잘못 잡았나 싶기도 하고, 

Unreal이 아닌 게임 개발에도 유용한 인재라는 것을 보이기 위해서는

Windows Programming과 DirectX를 사용한 것을 보여줘야 하지 않나 싶기도 합니다.

 

그래서 가능하면 현재 리팩토링을 끝내고 난 뒤에는,

Unreal 개발을 주 4회에서 2회로 줄이는 대신 Windows Programming을 적극적으로 해볼까 싶습니다.

'개발일지 > Treasure Hunter' 카테고리의 다른 글

20.06.29 개발일지  (0) 2020.06.29
20.06.25 개발일지 2  (0) 2020.06.25
20.06.22 개발일지  (0) 2020.06.22
20.06.20 개발일지  (0) 2020.06.20
20.06.15 개발일지  (0) 2020.06.17

Multiplay 기능을 지원 할 때 단순히 UE_LOG를 사용하면

테스트 중 접속한 Client 갯수만큼 Log가 출력되어 정확히 판단하기 힘들 때가 있다.

 

이에 대해 여러가지 답안을 모색 했으나, 비루한 영어 실력과 그에 비해 광활한 답안으로 인해 명확한 답을 찾지 못했다.

그리고 마침내, 한가지 제대로 동작하는 케이스가 있어 이를 따로 작성한다.

 

https://forums.unrealengine.com/development-discussion/c-gameplay-programming/49577-how-to-know-on-client-side-if-an-actor-is-own-by-the-current-network-connection

 

How to know on client side if an actor is "own" by the current network connection - Unreal Engine Forums

For gameplay programmers writing C++ code.

forums.unrealengine.com

혹 나와 같은 문제를 겪은 사람이 이 글을 보고 보다 빠르게 문제를 해결하기를 바란다.

오늘은 Merge Sort를 구현해 보았습니다만 Memory 초과로 인해 통과하지 못했습니다.

더보기
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

void MergeSort(vector<int>& List)
{
    if (List.size() < 5)
    {
        make_heap(List.begin(), List.end());
        sort_heap(List.begin(), List.end());
    }
    else
    {
        vector<int> front(List.begin(), List.begin() + List.size() / 2);
        vector<int> end(List.begin() + List.size() / 2, List.end());
        List.clear();
        MergeSort(front);
        MergeSort(end);
        for (unsigned int frontIndex = 0, endIndex = 0; frontIndex + endIndex < front.size() + end.size();)
        {
            if (frontIndex >= front.size())
            {
                List.push_back(end[endIndex++]);
            }
            else if (endIndex >= end.size())
            {
                List.push_back(front[frontIndex++]);
            }
            else
            {
                List.push_back(front[frontIndex] < end[endIndex] ? front[frontIndex++] : end[endIndex++]);
            }
        }
    }
}

int main()
{
    int size = 0, temp = 0;
    vector<int> list;
    
    cin >> size;
    for (unsigned int i = 0; i < size; ++i)
    {
        cin >> temp;
        list.push_back(temp);
    }

    MergeSort(list);
    
    for (const auto& iter : list)
    {
        cout << iter << endl;
    }
}

 

코드를 보면 알다싶이 재귀문에서 vector를 새로 생성하고 있습니다.

아마 이 때문에 문제가 생긴 것 같습니다.

내일은 이것을 하나의 Vector만 사용하여 Memory를 최소한으로 사용하는 Merge Sort로 고쳐보겠습니다.

Climb 상태에서 그 끝에 다다라서 상태 해제 될 때 Animation이 정상적으로 재생되지 않는 문제를 만져보았습니다.

오늘 시도한 방법은 DisableInput을 이용한 입력 제한이었습니다.

하지만 DisableInput을 해도 문제는 해결되지 않았습니다.

그러다가 문뜩 로그를 보니까, Montage 재생이 두번 출력되고,

Montage 재생 종료 이벤트도 두번 출력이 되는 것을 발견하였습니다.

 

Montage 재생 로그는 이동 입력 횟수에 따라 유동적이지만, 아무튼 최소 두번은 출력 되었습니다.

먼저, 두번 출력되는 것은 두 개의 Client가 모두 출력이 되서 그런 것으로 예상합니다.

 

그렇다면 Montage는 정상적으로 재생이 되고 있고, 정상적으로 종료 되고 있다는 결론에 도달합니다.

저는 여기서 Montage 재생이 정상적이지 못한 이유는

RootMotion을 해놓은 것이 모종의 문제가 있는 것으로 예상을 합니다.

 

실제 움직임도 움찔움찔 거리다가 Climb 상태가 해제 되는데,

중심을 움직이는 RootMotion이 벽에 부딛쳐서 재생이 안되는 것 같습니다.

 

내일은 우선 Montage가 정상 재생되는지 확인하기 위해 Montage End 로그에 

Exit Montage가 재생 중인지 조건을 걸어볼 예정입니다.

그리고 RootMotion쪽을 좀더 검색해볼 예정입니다.

당장 생각나는 방법은 Character를 teleport 하여 벽에 걸리지 않는 곳으로 보내고,

Montage가 정상 재생되는지 확인하는 것입니다.

 

만성 두드러기가 나서 항히스타민제를 복용하다보니 미친듯이 잠이 쏟아지고 있습니다.

게다가 최근에 여러 회사에 지원을 했고, 2곳에서 테스트를 봤으며 한곳에서 테스트를 추가로 볼 예정입니다.

아무래도 결과를 기다리는데 느낌이 좋은 곳이 있다 보니 긴장이 많이 풀어집니다.

하루에 개발에 투자하는 시간은 조금 줄어들겠지만, 그래도 빼먹는 날이 없도록 집중하겠습니다.

+ Recent posts