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로, 자세한 내용은 아래 링크로 대체한다.
그렇다면 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에서 선언한다.
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에서는 사용할 수 없다.
// 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들을 실행시킬지를 나타낸다.
그 아래에 PIXBeginEVent, PIXEndEvent는 PIX라는 라이브러리에서 선언된 함수이다.
PIX event를 이용해 특정 구역의 함수가 CPU에서 작동해야 하는지, 아니면 GPU에서 작동해야 하는지를 표시해주기도 하고, 중요한 작업을 표현하기도 한다.
Windows SDK에서는 오래된 버전인 pix.h를 제공한다. 이는 현재 제공 중인 pix3.h와 비슷한 기능들을 제공하고 있지만, PIX와는 쓸모 없고 호환되지 않는다.
https://devblogs.microsoft.com/pix/winpixeventruntime/
마지막으로 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/
블로그에 정리된 내용을 옮겨오자면
- C++로 WinRT API를 사용할 수 있는 통로는 C++/CX와 WRL 두가지가 있다.
- WRL은 데스크탑 어플리케이션에서 WinRT API를 호출하거나 MS내부에서 사용할 목적으로 만들었던 것으로 보인다.
- MS에선 WRL로의 개발을 장려하고 싶지 않았다. C++로 개발하는 경우 C++/CX를 권장했다.
- Windows 8.x가 실패하고 새로운 국면에 접어들었다. 사람들은 여전히 데스크탑 어플리케이션을 원했고 데스크탑 어플리케이션은 Windows에 있어 여전히 중요한 영역이었다.
- 새로운 펜.잉크 API, Surface Dial과 같은 디바이스들을 데스크탑에서 사용해야할 필요가 있었고 WRL에 대해서 다시 신경을 쓰게 된것으로 보인다.
- 최근 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
https://docs.microsoft.com/en-us/cpp/cppcx/threading-and-marshaling-c-cx?view=vs-2019
MS에서 agile은 말 그대로 agile한 object를 지칭하는 것 같다.
그 과정에 Threading과 Marshaling이 나오는데, Marshaling은 다음을 참고하자.
결국 agile은 Object를 빠르게 공유할 수 있는 것들. 혹은 그 속성을 지칭한다.
Object가 agile하려면 병렬화(threading)이나 직렬화(Marshaling)가 가능해야 하는데,
이것이 불가능한 것들을 가능한 형태로 변경 할 수 있도록 기능을 제공하는 것이 agile.h라고 생각된다.
concrt.h는 동시성 런타임과 관련된 header이다.
관련 내용을 찾아보았는데, 이 내용이 코드 내에서 어디에 어떻게 사용되었는지 파악하지 못했다.
우선은 관련 링크를 참조한다.
이번 챕터의 최종 목표는 2가지이다.
1. 예시 Template 코드에서 Rendering 부분만 지워서 해당 부분에 필요한 내용만 채워넣으면 어떤 예시라도 바로바로 테스트가 가능한 형태로 가공하기.
2. Window Desktop Application으로 동일한 기능 구현하기.
제대로 시작하기 전에는 1번이 오래 걸리고 2번이 생각보다 쉽게 갈 것 같았다.
하지만 막상 어느정도 정리하고 나니까 1번이 가시권이고 2번이 까마득하다.
그도 그럴게 MS가 만든 Template라서 당연할 수 있지만, 코드 자체가 성능 면을 상당히 세심하게 고려하고 짜여졌다.
때문에 우리처럼 이를 보고 처음 공부하려는 사람들은 어려움이 적지 않다.
하물며 이걸 이해해가면서 다른 방식으로 프로그래밍이라니... 눈물이 앞을 가린다.
일단은 1번을 먼저 해놓고, 2번을 고민해보려 한다.
다음에는 앞에서 빼먹었던 ExampleCreationMain, DeviceResources 코드들 중심으로 분석을 하려 한다.
그 뒤에는 책에서의 입문 chapter를 정리하고, 예시 코드를 실제 이 프로젝트에서 구현해보겠다.
'내용정리 > DirectX12' 카테고리의 다른 글
07. DirectX12 template 프로젝트 분석 05 - 20.06.23 (0) | 2020.06.23 |
---|---|
07. DirectX12 template 프로젝트 분석 04 - 20.06.16 (0) | 2020.06.16 |
07. DirectX12 template 프로젝트 분석 02 - 20.06.05 (0) | 2020.06.05 |
07. DirectX12 template 프로젝트 분석 01 - 20.05.29 (0) | 2020.05.29 |
06. DirectXMath.h에서의 Matrix, Transformation 관련 기능 (0) | 2020.05.29 |