前回
リファクタリング
前の記事では、レンダリングに必要な最小限のものを見ました。
1つのcppファイルのmainメソッドですべてを処理しました。
これからはモデルを読み込んでレンダリングしたり、モデルのタイプによって異なる処理をする必要があるため、これまでに作成したコードを一度整理する必要があります。
どのように構成するかをすべてお見せするのは意味がないので、私がどのように整理したかを大まかにお話しします。別の方法で整理する方が良いと思われる場合は、ぜひ自分でリファクタリングしてみてください。
Application クラス
私はウィンドウの生成とメインループを持つクラスとして、Applicationというクラスを作成しました。
実装部は次のようになっています。
class Application
{
public:
bool Init();
void Run();
void Terminate();
SIZE GetWindowSize() const;
~Application();
private:
WNDCLASSEX mWindowClass;
HWND mHwnd;
HINSTANCE mhInstance;
void CreateGameWindow(HWND& hwnd, WNDCLASSEX& windowClass);
Application();
Application(const Application&) = delete;
void operator=(const Application&) = delete;
};
Initではウィンドウを生成するための作業を行い、その後追加されるオブジェクトの初期化もこの部分で行います。
Runはメインループを持っています。
TerminateはRunを抜けると、プログラムが終了する前に呼び出されます。
mainメソッドを見ると、このようになるでしょう。
int main()
{
auto& app = Application::Instance();
if (!app.Init()) {
return -1;
}
app.Run();
app.Terminate();
return 0;
}
Dx12Wrapper クラス
このクラスは DirectX オブジェクトを整理したクラスです。
class Dx12Wrapper
{
template<typename T>
using ComPtr = Microsoft::WRL::ComPtr<T>;
public:
Dx12Wrapper(HWND hwnd);
~Dx12Wrapper();
void Clear();
void Update();
void EndDraw();
ComPtr<ID3D12Device> Device();
ComPtr<ID3D12GraphicsCommandList> CommandList();
ComPtr<IDXGISwapChain4> SwapChain();
DirectX::XMMATRIX GetViewMatrix() const;
DirectX::XMMATRIX GetProjectionMatrix() const;
private:
HRESULT InitializeDXGIDevice();
HRESULT InitializeCommand();
HRESULT CreateSwapChain(const HWND& hwnd);
SIZE mWindowSize;
ComPtr<IDXGIFactory6> mDXGIFactory = nullptr;
ComPtr<ID3D12Device> mDevice = nullptr;
ComPtr<ID3D12CommandAllocator> mCmdAllocator = nullptr;
ComPtr<ID3D12GraphicsCommandList> mCmdList = nullptr;
ComPtr<ID3D12CommandQueue> mCmdQueue = nullptr;
ComPtr<IDXGISwapChain4> mSwapChain = nullptr;
ComPtr<ID3D12DescriptorHeap> mRtvHeaps = nullptr;
std::vector<ID3D12Resource*> mBackBuffers;
std::unique_ptr<D3D12_VIEWPORT> mViewport;
std::unique_ptr<D3D12_RECT> mScissorRect;
ComPtr<ID3D12Fence> mFence = nullptr;
UINT64 mFenceVal = 0;
};
前の記事で生成したDirectXオブジェクトを保持し、必要な時にデバイス、コマンドリスト、スワップチェインを返すことができるようにします。
Clearでは、バックバッファのリソース状態をレンダーターゲットに変更し、レンダーターゲットを設定して黒でクリアします。
Updateは、毎フレーム必要な処理を入れるメソッドです。
EndDrawでは、バックバッファのリソース状態をPresent状態に変更し、命令を実行してフェンスで命令が終わるのを待ち、次にPresentします。
そして、コンストラクタでDirectXの初期化を行う前に、このようなコードを追加してください。
#ifdef _DEBUG
ID3D12Debug* debugLayer = nullptr;
auto result = D3D12GetDebugInterface(IID_PPV_ARGS(&debugLayer));
debugLayer->EnableDebugLayer();
debugLayer->Release();
#endif
これはデバッグレイヤーを有効にするコードです。
DirectXプログラミングをしていると、設定ミスや何らかの理由でオブジェクトの作成に失敗することがあります。通常、このような場合、失敗の理由を見つけるのは難しいです。
このようにデバッグレイヤーを有効にすると、VisualStudioの出力ウィンドウにDirectXオブジェクトの作成に失敗した場合、その理由が表示されます。
プリプロセッサを使用して、デバッグ環境でデバッグレイヤーを有効にできるようにしましょう。
Render クラス
私はこのクラスで全体的なレンダリングの順序を管理しようとしています。Dx12WrapperはDirectXのAPIを呼び出す手段として使用し、Dx12Wrapperオブジェクトを持って以降に追加されるモデルレンダリングを含め、全体的な呼び出し順序をここで定義します。
class Render
{
public:
Render(std::shared_ptr<Dx12Wrapper>& dx);
void Frame();
private:
void Update() const;
void DrawFrame() const;
void EndOfFrame() const;
}
今は特別なことはありませんが、この次にPMXモデルのレンダリングに関するオブジェクトをここで持つ予定です。
このRenderオブジェクトをApplicationオブジェクトが持つことになり、RunでRenderのFrameが呼ばれる構造になるでしょう。
class Application
{
public:
bool Init();
void Run();
void Terminate();
SIZE GetWindowSize() const;
~Application();
private:
WNDCLASSEX mWindowClass;
HWND mHwnd;
HINSTANCE mhInstance;
std::shared_ptr<Render> mRender = nullptr; //追加
void CreateGameWindow(HWND& hwnd, WNDCLASSEX& windowClass);
Application();
Application(const Application&) = delete;
void operator=(const Application&) = delete;
};
void Application::Run()
{
ShowWindow(mHwnd, SW_SHOW);
while (true)
{
if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if (msg.message == WM_QUIT)
{
break;
}
if (Input::Instance()->Update() == false)
{
break;
}
mRender->Frame(); //毎フレームごとに呼ぶ
}
}
次回