이번에는 Chapter 6의 Box Example을
VS에서 제공하는 Windows Programming Template에 적용한 코드를 소개하려 합니다.
Project Introduction
Common 폴더 안의 코드들은 DirectX를 사용하는데 필요한 몇가지 코드들을 모아놓은 것입니다.
즉, 책에서 작성되고, 사용된 코드들의 집합체라는 것입니다.
실질적인 구현체인 BoxApp은 Example 폴더 안에 있습니다.
앞으로 추가적인 예시 코드들은 Example 폴더 안에 작성될 예정입니다.
폴더에 들어있지 않은 코드 중 DirectX12ExampleCode는 Main 함수가 적혀있습니다.
이 외에는 Window Programming Template에서 제공된 Header들로,
여기에 예시 작성에 필요한 헤더나 선언들을 몇가지 추가 했습니다.
마지막으로 ReosurceFiles은 마찬가지로 Template에서 제공된 것들입니다.
앞으로도 신경쓰지 않으면 됩니다.
targetver.h
#pragma once
// Including SDKDDKVer.h defines the highest available Windows platform.
// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and
// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h.
#include <SDKDDKVer.h>
항목을 따로 두긴 했지만, 설명에 적힌것과 마찬가지로 Windows platform 버전을 지정하는 것입니다.
앞으로도 이 버전은 따로 건드릴 예정이 없으니 그대로 방치하면 됩니다.
resource.h
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by DirectX12ExampleCode.rc
//
#define IDC_MYICON 2
#define IDD_DIRECTX12EXAMPLECODE_DIALOG 102
#define IDS_APP_TITLE 103
#define IDD_ABOUTBOX 103
#define IDM_ABOUT 104
#define IDM_EXIT 105
#define IDI_DIRECTX12EXAMPLECODE 107
#define IDI_SMALL 108
#define IDC_DIRECTX12EXAMPLECODE 109
#define IDR_MAINFRAME 128
#define ID_EXAMPLE_CHAPTER6 32771
#define IDC_STATIC -1
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC 1
#define _APS_NEXT_RESOURCE_VALUE 129
#define _APS_NEXT_COMMAND_VALUE 32774
#define _APS_NEXT_CONTROL_VALUE 1000
#define _APS_NEXT_SYMED_VALUE 110
#endif
#endif
Window에 사용되는 Resource들에 대한 키 값이 지정되어 있는 곳입니다.
여기서 눈여겨 봐야 할 점이라면 ID_EXAMPLE_CHAPTER6입니다.
이는 Example 메뉴에서 Chapter 6를 클릭했을 시 Box Example이 출력되게 하기 위해 추가된 것입니다.
다만 아직 이 기능까지 구현을 하지 못했고, 구조상 아직은 런타임상에서 교체를 할 수 없습니다.
그러니 예시가 늘어날 때마다 새로운 항목만 추가를 합시다.
framework.h
// header.h : include file for standard system include files,
// or project specific include files
//
#pragma once
#include "targetver.h"
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
// Windows Header Files
#include <windows.h>
// C RunTime Header Files
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <tchar.h>
Template상에서 필요한 헤더들이 선언된 곳입니다.
그즉슨, Window 제작에 필요한 것들이 선언되었다는 것입니다.
아직 완벽히 작성되지 않았지만, 앞으로 Window 제작에 필요한 헤더는 다 이곳으로 이동될 것입니다.
하지만 틀만 완성되면 그 이후로는 DirectX 구현이라 이부분은 크게 게의치 않아도 될 것 같습니다.
Common
DXException
#pragma once
#include <Windows.h>
#include <string>
class DXException
{
public:
DXException();
DXException(HRESULT hr, const std::wstring& functionName, const std::wstring& filename, int lineNumber);
std::wstring ToString() const;
HRESULT ErrorCode = S_OK;
std::wstring FunctionName;
std::wstring FileName;
int LineNumber = -1;
};
#include <comdef.h>
#include "DXException.h"
DXException::DXException(HRESULT hr, const std::wstring& functionName, const std::wstring& filename, int lineNumber)
: ErrorCode(hr), FunctionName(functionName), FileName(filename), LineNumber(lineNumber)
{
}
std::wstring DXException::ToString() const
{
_com_error err(ErrorCode);
std::wstring msg = err.ErrorMessage();
return FunctionName + L" failed in " + FileName + L"; line " + std::to_wstring(LineNumber) + L"; error: " + msg;
}
DXException은 DirectX Example 작성에 있어서 발생하는 에러를 새로운 Window에 출력하는 역할을 한다.
이는 따로 제공하는 코드는 아니고, 책에서 따로 작성한 코드이다.
이는 HRESULT 값을 통해 오류가 발생한 함수 명, 호출된 파일 명, 줄 번호를 담고 있다.
여기서 comdef.h라는 낯선 헤더가 눈에 띌 것이다.
이는 ToString에서 사용하는 _com_error 클래스를 담고 있으며, COM types에서 error type을 정의한다.
https://docs.microsoft.com/ko-kr/cpp/cpp/compiler-com-support-classes?view=vs-2019
GameTimer
#ifndef GAMETIMER_H
#define GAMETIMER_H
class GameTimer
{
public:
GameTimer();
float TotalTime()const; // in seconds
float DeltaTime()const; // in seconds
void Reset(); // Call before message loop.
void Start(); // Call when unpaused.
void Stop(); // Call when paused.
void Tick(); // Call every frame.
private:
double mSecondsPerCount;
double mDeltaTime;
__int64 mBaseTime;
__int64 mPausedTime;
__int64 mStopTime;
__int64 mPrevTime;
__int64 mCurrTime;
bool mStopped;
};
#endif // GAMETIMER_H
#include <Windows.h>
#include <memory>
#include "GameTimer.h"
GameTimer::GameTimer()
: mSecondsPerCount(0.0), mDeltaTime(0.0), mBaseTime(0), mStopTime(0),
mPausedTime(0), mPrevTime(0), mCurrTime(0), mStopped(false)
{
__int64 countsPerSec;
QueryPerformanceFrequency((LARGE_INTEGER*)&countsPerSec);
mSecondsPerCount = 1 / static_cast<double>(countsPerSec);
}
float GameTimer::TotalTime() const
{
return static_cast<float>((((mStopped ? mStopTime : mCurrTime) - mPausedTime) - mBaseTime) * mSecondsPerCount);
}
float GameTimer::DeltaTime() const
{
return static_cast<float>(mDeltaTime);
}
void GameTimer::Reset()
{
__int64 currTime;
QueryPerformanceCounter((LARGE_INTEGER*)&currTime);
mBaseTime = currTime;
mPrevTime = currTime;
mStopTime = 0;
mStopped = false;
}
void GameTimer::Start()
{
__int64 startTime;
QueryPerformanceCounter((LARGE_INTEGER*)&startTime);
if (mStopped)
{
mPausedTime += (startTime - mStopTime);
mPrevTime = startTime;
mStopTime = 0;
mStopped = false;
}
}
void GameTimer::Stop()
{
if (!mStopped)
{
__int64 currTime;
QueryPerformanceCounter((LARGE_INTEGER*)(&currTime));
mStopTime = currTime;
mStopped = true;
}
}
void GameTimer::Tick()
{
if (mStopped)
{
mDeltaTime = 0.0;
return;
}
__int64 currTime;
QueryPerformanceCounter((LARGE_INTEGER*)&currTime);
mCurrTime = currTime;
mDeltaTime = (mCurrTime - mPrevTime) * mSecondsPerCount;
mPrevTime = mCurrTime;
if (mDeltaTime < 0.0)
{
mDeltaTime = 0.0;
}
}
GameTimer는 DirectX 예시에서 시간과 관련된 기능들이 구현되어 있습니다.
게임 개발 할 때 초당 Update를 처리하는 Tick이라던가.
이 Tick이 호출되는 주기라던가. 경과 시간이라던가.
GameTimer 또한 지원해주는 함수는 아닙니다.
VS에서 지원하는 DirectX12 Template에서는 Windows에서 사용하는 Time 관련 class를 사용하기 때문이죠.
하지만 그쪽 예시는 기본적으로 GPU 사용과 Multicore를 기본 전제로 깔면서,
Windows App Programming에서 요구하는 최신 문법들을 사용하기 때문에 입문을 하는데에 큰 어려움이 따릅니다.
때문에 가능하면 이 함수를 따로 구현하는 것을 추천합니다.
대부분의 함수 기능과 변수 역할은 코드를 읽으면 충분히 이해가 갈 것입니다.
그 중 낯선 것이 있다면 QueryPerformanceCounter, QueryPerformanceFrequency인데,
이는 고해상도 타이머의 Clock수와 주파수를 받아오는 함수입니다.
대체로 CPU의 것을 주로 사용합니다.
http://www.tipssoft.com/bulletin/board.php?bo_table=FAQ&wr_id=735
Common
#pragma comment(lib, "d3dcompiler.lib")
#pragma comment(lib, "D3D12.lib")
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "dxcompiler.lib")
#pragma once
#include <Windows.h>
#include <wrl.h>
#include <windowsx.h>
#include <d3d12.h>
#include <dxgi1_6.h>
#include <d3dcompiler.h>
#include <dxcapi.h>
#include <DirectXMath.h>
#include <pix.h>
#include <DirectXPackedVector.h>
#include <DirectXColors.h>
#include <DirectXCollision.h>
#include <string>
#include <vector>
#include <array>
#include <unordered_map>
#include <algorithm>
#include <memory>
#include "GameTimer.h"
#include "DXException.h"
#include "../resource.h"
#include <iostream>
#if defined(DEBUG) || defined(_DEBUG)
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
#endif
#define MAX_LOADSTRING 100
// Global Variables:
//HINSTANCE hInst; // current instance
//WCHAR szTitle[MAX_LOADSTRING]; // The title bar text
//WCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name
inline std::wstring AnsiToWString(const std::string& str)
{
WCHAR buffer[512];
MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, buffer, 512);
return std::wstring(buffer);
}
#ifndef ThrowIfFailed
#define ThrowIfFailed(x) \
{ \
HRESULT hr__ = (x); \
std::wstring wfn = AnsiToWString(__FILE__); \
if(FAILED(hr__)) \
{ \
throw DXException(hr__, L#x, wfn, __LINE__);\
} \
}
#endif
template<typename T>
static T Clamp(const T& x, const T& low, const T& high)
{
return x < low ? low : (x > high ? high : x);
}
static UINT calcConstantBufferByteSize(UINT byteSize)
{
return (byteSize + 255) & ~255;
}
static Microsoft::WRL::ComPtr<ID3DBlob> CompileShader(
const std::wstring& filename,
const D3D_SHADER_MACRO* defines,
const std::string& entrypoint,
const std::string& target)
{
UINT compileFlags = 0;
#if defined(DEBUG) || defined(_DEBUG)
compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#endif
HRESULT hr = S_OK;
Microsoft::WRL::ComPtr<ID3DBlob> byteCode = nullptr;
Microsoft::WRL::ComPtr<ID3DBlob> errors = nullptr;
hr = D3DCompileFromFile(filename.c_str(), defines, D3D_COMPILE_STANDARD_FILE_INCLUDE,
entrypoint.c_str(), target.c_str(), compileFlags, 0, &byteCode, &errors);
if (errors != nullptr)
{
OutputDebugStringA(static_cast<char*>(errors->GetBufferPointer()));
}
ThrowIfFailed(hr);
return byteCode;
}
//------------------------------------------------------------------------------------------------
// Row-by-row memcpy
inline void MemcpySubresource(
_In_ const D3D12_MEMCPY_DEST* pDest,
_In_ const D3D12_SUBRESOURCE_DATA* pSrc,
SIZE_T RowSizeInBytes,
UINT NumRows,
UINT NumSlices)
{
for (UINT z = 0; z < NumSlices; ++z)
{
BYTE* pDestSlice = reinterpret_cast<BYTE*>(pDest->pData) + pDest->SlicePitch * z;
const BYTE* pSrcSlice = reinterpret_cast<const BYTE*>(pSrc->pData) + pSrc->SlicePitch * z;
for (UINT y = 0; y < NumRows; ++y)
{
memcpy(pDestSlice + pDest->RowPitch * y,
pSrcSlice + pSrc->RowPitch * y,
RowSizeInBytes);
}
}
}
//------------------------------------------------------------------------------------------------
// All arrays must be populated (e.g. by calling GetCopyableFootprints)
inline UINT64 UpdateSubresources(
_In_ ID3D12GraphicsCommandList* pCmdList,
_In_ ID3D12Resource* pDestinationResource,
_In_ ID3D12Resource* pIntermediate,
_In_range_(0, D3D12_REQ_SUBRESOURCES) UINT FirstSubresource,
_In_range_(0, D3D12_REQ_SUBRESOURCES - FirstSubresource) UINT NumSubresources,
UINT64 RequiredSize,
_In_reads_(NumSubresources) const D3D12_PLACED_SUBRESOURCE_FOOTPRINT* pLayouts,
_In_reads_(NumSubresources) const UINT* pNumRows,
_In_reads_(NumSubresources) const UINT64* pRowSizesInBytes,
_In_reads_(NumSubresources) const D3D12_SUBRESOURCE_DATA* pSrcData)
{
// Minor validation
D3D12_RESOURCE_DESC IntermediateDesc = pIntermediate->GetDesc();
D3D12_RESOURCE_DESC DestinationDesc = pDestinationResource->GetDesc();
if (IntermediateDesc.Dimension != D3D12_RESOURCE_DIMENSION_BUFFER ||
IntermediateDesc.Width < RequiredSize + pLayouts[0].Offset ||
RequiredSize >(SIZE_T) - 1 ||
(DestinationDesc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER &&
(FirstSubresource != 0 || NumSubresources != 1)))
{
return 0;
}
BYTE* pData;
HRESULT hr = pIntermediate->Map(0, NULL, reinterpret_cast<void**>(&pData));
if (FAILED(hr))
{
return 0;
}
for (UINT i = 0; i < NumSubresources; ++i)
{
if (pRowSizesInBytes[i] > (SIZE_T)-1) return 0;
D3D12_MEMCPY_DEST DestData = { pData + pLayouts[i].Offset, pLayouts[i].Footprint.RowPitch, pLayouts[i].Footprint.RowPitch * pNumRows[i] };
MemcpySubresource(&DestData, &pSrcData[i], (SIZE_T)pRowSizesInBytes[i], pNumRows[i], pLayouts[i].Footprint.Depth);
}
pIntermediate->Unmap(0, NULL);
if (DestinationDesc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER)
{
D3D12_BOX SrcBox{ static_cast<UINT>(pLayouts[0].Offset), 0, 0, static_cast<UINT>(pLayouts[0].Offset + pLayouts[0].Footprint.Width), 1, 1 };
//CD3DX12_BOX SrcBox(UINT(pLayouts[0].Offset), UINT(pLayouts[0].Offset + pLayouts[0].Footprint.Width));
pCmdList->CopyBufferRegion(
pDestinationResource, 0, pIntermediate, pLayouts[0].Offset, pLayouts[0].Footprint.Width);
}
else
{
for (UINT i = 0; i < NumSubresources; ++i)
{
D3D12_TEXTURE_COPY_LOCATION Dst{ pDestinationResource, D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT, i + FirstSubresource };
D3D12_TEXTURE_COPY_LOCATION Src{ pIntermediate, D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT, pLayouts[i] };
//CD3DX12_TEXTURE_COPY_LOCATION Dst(pDestinationResource, i + FirstSubresource);
//CD3DX12_TEXTURE_COPY_LOCATION Src(pIntermediate, pLayouts[i]);
pCmdList->CopyTextureRegion(&Dst, 0, 0, 0, &Src, nullptr);
}
}
return RequiredSize;
}
//------------------------------------------------------------------------------------------------
// Heap-allocating UpdateSubresources implementation
inline UINT64 UpdateSubresources(
_In_ ID3D12GraphicsCommandList* pCmdList,
_In_ ID3D12Resource* pDestinationResource,
_In_ ID3D12Resource* pIntermediate,
UINT64 IntermediateOffset,
_In_range_(0, D3D12_REQ_SUBRESOURCES) UINT FirstSubresource,
_In_range_(0, D3D12_REQ_SUBRESOURCES - FirstSubresource) UINT NumSubresources,
_In_reads_(NumSubresources) D3D12_SUBRESOURCE_DATA* pSrcData)
{
UINT64 RequiredSize = 0;
UINT64 MemToAlloc = static_cast<UINT64>(sizeof(D3D12_PLACED_SUBRESOURCE_FOOTPRINT) + sizeof(UINT) + sizeof(UINT64)) * NumSubresources;
if (MemToAlloc > SIZE_MAX)
{
return 0;
}
void* pMem = HeapAlloc(GetProcessHeap(), 0, static_cast<SIZE_T>(MemToAlloc));
if (pMem == NULL)
{
return 0;
}
D3D12_PLACED_SUBRESOURCE_FOOTPRINT* pLayouts = reinterpret_cast<D3D12_PLACED_SUBRESOURCE_FOOTPRINT*>(pMem);
UINT64* pRowSizesInBytes = reinterpret_cast<UINT64*>(pLayouts + NumSubresources);
UINT* pNumRows = reinterpret_cast<UINT*>(pRowSizesInBytes + NumSubresources);
D3D12_RESOURCE_DESC Desc = pDestinationResource->GetDesc();
ID3D12Device* pDevice;
pDestinationResource->GetDevice(__uuidof(*pDevice), reinterpret_cast<void**>(&pDevice));
pDevice->GetCopyableFootprints(&Desc, FirstSubresource, NumSubresources, IntermediateOffset, pLayouts, pNumRows, pRowSizesInBytes, &RequiredSize);
pDevice->Release();
UINT64 Result = UpdateSubresources(pCmdList, pDestinationResource, pIntermediate, FirstSubresource, NumSubresources, RequiredSize, pLayouts, pNumRows, pRowSizesInBytes, pSrcData);
HeapFree(GetProcessHeap(), 0, pMem);
return Result;
}
static Microsoft::WRL::ComPtr<ID3D12Resource> CreateDefaultbuffer(
ID3D12Device* device,
ID3D12GraphicsCommandList* cmdList,
const void* initData,
UINT64 byteSize,
Microsoft::WRL::ComPtr<ID3D12Resource>& uploadBuffer)
{
Microsoft::WRL::ComPtr<ID3D12Resource> defaultBuffer;
D3D12_HEAP_PROPERTIES BeforeDefaultHeapProperty{ D3D12_HEAP_TYPE_DEFAULT, D3D12_CPU_PAGE_PROPERTY_UNKNOWN, D3D12_MEMORY_POOL_UNKNOWN, 1, 1 };
D3D12_RESOURCE_DESC BeforeDefaultResource{
D3D12_RESOURCE_DIMENSION_BUFFER,
0, byteSize, 1, 1, 1,
DXGI_FORMAT_UNKNOWN, 1, 0,
D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
D3D12_RESOURCE_FLAG_NONE };
ThrowIfFailed(device->CreateCommittedResource(
&BeforeDefaultHeapProperty,
D3D12_HEAP_FLAG_NONE,
&BeforeDefaultResource,
D3D12_RESOURCE_STATE_COMMON,
nullptr,
IID_PPV_ARGS(defaultBuffer.GetAddressOf())
));
D3D12_HEAP_PROPERTIES AfterUploadHeapProperty{ D3D12_HEAP_TYPE_UPLOAD, D3D12_CPU_PAGE_PROPERTY_UNKNOWN, D3D12_MEMORY_POOL_UNKNOWN, 1, 1 };
D3D12_RESOURCE_DESC AfterUploadResource{
D3D12_RESOURCE_DIMENSION_BUFFER,
0, byteSize, 1, 1, 1,
DXGI_FORMAT_UNKNOWN, 1, 0,
D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
D3D12_RESOURCE_FLAG_NONE };
ThrowIfFailed(device->CreateCommittedResource(
&AfterUploadHeapProperty,
D3D12_HEAP_FLAG_NONE,
&AfterUploadResource,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(uploadBuffer.GetAddressOf())
));
D3D12_SUBRESOURCE_DATA subResourceData = {};
subResourceData.pData = initData;
subResourceData.RowPitch = static_cast<LONG_PTR>(byteSize);
subResourceData.SlicePitch = subResourceData.RowPitch;
D3D12_RESOURCE_BARRIER BeforeBarrier;
ZeroMemory(&BeforeBarrier, sizeof(BeforeBarrier));
BeforeBarrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
BeforeBarrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
BeforeBarrier.Transition.pResource = defaultBuffer.Get();
BeforeBarrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COMMON;
BeforeBarrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST;
BeforeBarrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
cmdList->ResourceBarrier(1, &BeforeBarrier);
UpdateSubresources(cmdList, defaultBuffer.Get(), uploadBuffer.Get(), 0, 0, 1, &subResourceData);
D3D12_RESOURCE_BARRIER AfterBarrier;
ZeroMemory(&AfterBarrier, sizeof(AfterBarrier));
AfterBarrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
AfterBarrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
AfterBarrier.Transition.pResource = defaultBuffer.Get();
AfterBarrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
AfterBarrier.Transition.StateAfter = D3D12_RESOURCE_STATE_GENERIC_READ;
AfterBarrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
cmdList->ResourceBarrier(1, &AfterBarrier);
return defaultBuffer;
}
Common.h는 크게 3가지 기능을 가지고 있습니다.첫번째는 DirectX Library의 링크. 가장 위의 4개 줄이 이 역할을 맡습니다.두번째는 DirectX 예시 작성에 필요한 Header Include.세번째는 DirectX 예시 전반적으로 요구되는 공통 함수들이 선언되어 있습니다.원래 예시에서는 D3DUtill이었으며, 위에서 설명한 DXException이나 GameTimer도 원래 이 안에 선언되어 있었습니다.하지만 Class 단위의 것들은 별도의 파일로 분리를 하면서 따로 작성이 되었습니다.
우선은 이대로 사용을 하고, 어느정도 추가 될 내용이 없어지면 다시 한번 정리를 할 예정입니다.
MeshGeometry
#pragma once
#include "Common.h"
struct SubmeshGeometry
{
UINT IndexCount = 0;
UINT StartIndexLocation = 0;
INT BaseVertexLocation = 0;
DirectX::BoundingBox Bounds;
};
struct MeshGeometry
{
std::string Name;
Microsoft::WRL::ComPtr<ID3DBlob> VertexBufferCPU = nullptr;
Microsoft::WRL::ComPtr<ID3DBlob> IndexBufferCPU = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource> VertexBufferGPU = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource> IndexBufferGPU = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource> VertexBufferUploader = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource> IndexBufferUploader = nullptr;
UINT VertexByteStride = 0;
UINT VertexBufferByteSize = 0;
DXGI_FORMAT IndexFormat = DXGI_FORMAT_R16_UINT;
UINT IndexBufferByteSize = 0;
std::unordered_map<std::string, SubmeshGeometry> DrawArgs;
D3D12_VERTEX_BUFFER_VIEW VertexBufferView() const
{
D3D12_VERTEX_BUFFER_VIEW vbv;
vbv.BufferLocation = VertexBufferGPU->GetGPUVirtualAddress();
vbv.StrideInBytes = VertexByteStride;
vbv.SizeInBytes = VertexBufferByteSize;
return vbv;
}
D3D12_INDEX_BUFFER_VIEW IndexBufferView() const
{
D3D12_INDEX_BUFFER_VIEW ibv;
ibv.BufferLocation = IndexBufferGPU->GetGPUVirtualAddress();
ibv.Format = IndexFormat;
ibv.SizeInBytes = IndexBufferByteSize;
return ibv;
}
};
MeshGeometry는 DirectX 예시 코드에서 사용하는 Model 객체입니다.
Resource를 읽고 이를 기반으로 Vertex, Index를 내부에서 관리하면서 BufferView를 제공합니다.
간단하게 "Resource를 이렇게 관리한다."와 "이러이러한 정보들을 받아올 수 있다."를 파악하면 될 것 같습니다.
UploadBuffer
#pragma once
#include "Common.h"
template<typename T>
class UploadBuffer
{
public:
UploadBuffer(ID3D12Device* device, UINT elementCount, bool isConstantBuffer) : mIsConstantBuffer(isConstantBuffer)
{
mElementByteSize = sizeof(T);
if (isConstantBuffer)
{
mElementByteSize = calcConstantBufferByteSize(sizeof(T));
}
D3D12_HEAP_PROPERTIES HeapProperty;
HeapProperty.Type = D3D12_HEAP_TYPE_UPLOAD;
HeapProperty.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
HeapProperty.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
HeapProperty.CreationNodeMask = 1;
HeapProperty.VisibleNodeMask = 1;
D3D12_RESOURCE_DESC Resource;
Resource.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
Resource.Alignment = 0;
Resource.Width = mElementByteSize * static_cast<UINT64>(elementCount);
Resource.Height = 1;
Resource.DepthOrArraySize = 1;
Resource.MipLevels = 1;
Resource.Format = DXGI_FORMAT_UNKNOWN;
Resource.SampleDesc.Count = 1;
Resource.SampleDesc.Quality = 0;
Resource.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
Resource.Flags = D3D12_RESOURCE_FLAG_NONE;
ThrowIfFailed(device->CreateCommittedResource(
&HeapProperty,
D3D12_HEAP_FLAG_NONE,
&Resource,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&mUploadBuffer)
));
ThrowIfFailed(mUploadBuffer->Map(0, nullptr, reinterpret_cast<void**>(&mMappedData)));
}
UploadBuffer(const UploadBuffer& rhs) = delete;
UploadBuffer& operator=(const UploadBuffer& rhs) = delete;
~UploadBuffer()
{
if (mUploadBuffer != nullptr)
{
mUploadBuffer->Unmap(0, nullptr);
}
mMappedData = nullptr;
}
ID3D12Resource* Resource() const
{
return mUploadBuffer.Get();
}
void CopyData(int elementIndex, const T& data)
{
memcpy(&mMappedData[elementIndex * mElementByteSize], &data, sizeof(T));
}
private:
Microsoft::WRL::ComPtr<ID3D12Resource> mUploadBuffer;
BYTE* mMappedData = nullptr;
UINT mElementByteSize = 0;
bool mIsConstantBuffer = false;
};
UploadBuffer는 DirectX에서 사용하는 Buffer의 기본적인 기능을 구현한 것입니다.
복사생성자와 대입연산자 생성을 막아서 복사를 하지 못하게 하였고,
별도의 데이터 복사나 버퍼의 포인터를 따로 제공하는 함수가 있는 Proxy Class입니다.
사실 여기서부터 기존 코드와 차이가 발생하는데,
Windows에서 제공하는 구현체를 사용하지 않은 탓에 굉장히 긴 초기화 과정을 거치는 경우가 있습니다.
처음에는 배울 때에는 외부 코드를 사용하지 않는 것이 좋다고 생각 했습니다.
하지만 한번 해보니, 아무리 생각해도 이러한 코드들 없이 개발 하는 것은 좀 말이 안된다는 생각이 들었습니다.
그래서 이 부분은 코드 설명이 다 끝난 뒤, 대대적으로 수정을 할 예정입니다.
다음에는 DirectX 기본 코드인 D3DApp을 살펴보도록 하겠습니다.
'내용정리 > DirectX12' 카테고리의 다른 글
10. Box Example 3 - BoxApp, WinMain (0) | 2020.09.15 |
---|---|
10. Box Example 2 - D3DApp (0) | 2020.09.08 |
20.08.25 개발일지 (0) | 2020.08.25 |
20.08.21 개발일지 (0) | 2020.08.21 |
20.08.18 개발일지 (0) | 2020.08.18 |