1
2

DirectX12でミクさんを躍らせてみよう2-初期化

Last updated at Posted at 2024-10-01

前回

ウィンドウを表示する

ウィンドウプログラミングを経験したことがある方は、すでに知っている内容だと思います。

#include <Windows.h>
#include <tchar.h>

ウィンドウAPIを使用するためにヘッダーをインクルードします。

UnicodeおよびANSI文字を両方サポートする文字列を処理しやすくするために、tchar.hもインクルードします。

ここからはmain()に入る内容です。
まずウィンドウクラスを登録しましょう。

WNDCLASSEX w = {};

w.cbSize = sizeof(WNDCLASSEX);
w.lpfnWndProc = (WNDPROC)WindowProcedure;
w.lpszClassName = _T("DX12Sample");
w.hInstance = GetModuleHandle(nullptr);

RegisterClassEx(&w);

ウィンドウクラスを初期化し、ウィンドウプロシージャ、クラス名を登録します。
lpszClassNameは、このプログラムで複数のウィンドウを作成しない場合、どんな名前でも構いません。

プロシージャは次のように作成します。

LRESULT WindowProcedure(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
	if (msg == WM_DESTROY)
	{
		PostQuitMessage(0);
		return 0;
	}

	return DefWindowProc(hwnd, msg, wparam, lparam);
}

このプロシージャ関数を通じてウィンドウメッセージを処理します。
WM_DESTROYはウィンドウが破壊されるときに発生します。
WM_DESTROYメッセージを受け取ると、アプリケーションを終了するようにします。

RECT wrc = { 0, 0, 1600, 800 };
AdjustWindowRect(&wrc, WS_OVERLAPPEDWINDOW, false);

HWND hwnd = CreateWindow(w.lpszClassName,
	_T("DX12test"),
	WS_OVERLAPPEDWINDOW,
	CW_USEDEFAULT,
	CW_USEDEFAULT,
	wrc.right - wrc.left,
	wrc.bottom - wrc.top,
	nullptr,
	nullptr,
	w.hInstance,
	nullptr);

ShowWindow(hwnd, SW_SHOW);

私は1600 x 800の解像度に設定しました。
このサイズは、後でレンダリングする際に最終的に描画される画面のサイズです。
もっと大きくしたり小さくしたりしても問題はありません。
当然、より大きな解像度で3Dレンダリングを行うと、もっとコストがかかるでしょう。
私はWS_OVERLAPPEDWINDOWで最も一般的なウィンドウが表示されるように設定しました。
上部のウィンドウ名は「DX12test」と表示されるようにしました。

MSG msg = {  };

while (true)
{
	if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	if (msg.message == WM_QUIT)
	{
		break;
	}
}

メインループです。
PeekMessageを通じてユーザーの入力やシステムメッセージを処理するようにします。
WM_QUITメッセージが来たらループを終了するようにします。
まだDirectXに関するコードを入れていませんが、ここで毎フレームレンダリングすることになります。

UnregisterClass(w.lpszClassName, w.hInstance);
return 0;

while ループを抜けると、ウィンドウクラスを解除してプログラムを終了します。

ここまでやってビルドして実行してみると、次のようなウィンドウが表示されるでしょう。
image.png
まだ何もしていないので真っ白なウィンドウが表示されるだけです。

ここまでは、一般的にWindowsプログラミングを初めて始めるときに行う内容と似ています。
では、DirectXの初期化を進めてみましょう。

DirectX12 初期化

DirectX APIを使用するために必要なヘッダーファイルを追加しましょう。
コンパイル時に必要なライブラリをリンカーに含めるようにします。

#include <d3d12.h>
#include <dxgi1_6.h>

#pragma comment(lib, "d3d12.lib")
#pragma comment(lib, "dxgi.lib")

DirectXを使用するために最初に初期化するオブジェクトは次の3つです。

ID3D12Device* mDev = nullptr;
IDXGIFactory6* mDxgiFactory = nullptr;
IDXGISwapChain4* mSwapChain = nullptr;

こちらのオブジェクトを実際に初期化して使ってみると、どのような場面で利用されるかおおよそ理解できるかと思いますが、DXGIという名前は少し馴染みがないかもしれません。

DXGI(DirectX Graphics Infrastructure)はAPIというよりはドライバーに近いものです。ディスプレイの出力に関する機能を制御するためのものだと覚えておいてください。

HRESULT D3D12CreateDevice(
  [in, optional]  IUnknown          *pAdapter,
                  D3D_FEATURE_LEVEL MinimumFeatureLevel,
  [in]            REFIID            riid,
  [out, optional] void              **ppDevice
);

ID3D12Deviceを初期化する関数のドキュメント内容です。

最初のパラメータはアダプタのポインタです。アダプタはグラフィックボードドライバと考えればいいです。これはとりあえずnullptrにすると自動的にアダプタが設定されます。しかし、もし使用中の環境が複数のグラフィックボードを持っていて、そのうちの1つを使用しなければならない場合は設定が必要です。とりあえず、私はグラフィックボードが1つなのでnullptrにしておきます。.

2 番目のパラメーターは列挙型 D3D_FEATURE_LEVEL です。この記事を執筆している現在の最高レベルは D3D_FEATURE_LEVEL_12_2 です。しかし、選択したドライバーがそのレベルに対応していない場合、この初期化関数は失敗を返します。

最後の2つのパラメーターが残っていますが、最後のポインターがID3D12Deviceの本体であることは推測できますが、その前のREFIDは非常に馴染みがありません。これは関数が受け取るオブジェクトの型を識別するために使用されるIDです。しかし、これを直接取得するのは非常に複雑なので、IID_PPV_ARGSマクロを使用します。このマクロは他のDirectX APIでも引き続き使用されるため、覚えておいてください。

実際に初期化するコードは次のとおりです。

HRESULT result = D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_12_2, IID_PPV_ARGS(&mDev));

しかし、先ほど話したように、現在使用しているグラフィックボードが関数に渡されたレベルに対応していない場合、失敗することになります。したがって、渡されたレベルが対応できない状況に備えて、レベルを一つずつ下げながら設定する処理が必要です。

このように

D3D_FEATURE_LEVEL levels[] =
	{
		D3D_FEATURE_LEVEL_12_1,
		D3D_FEATURE_LEVEL_12_0,
		D3D_FEATURE_LEVEL_11_1,
		D3D_FEATURE_LEVEL_11_0
	};

	D3D_FEATURE_LEVEL featureLevel;

	for (auto lv : levels)
	{
		if (D3D12CreateDevice(nullptr, lv, IID_PPV_ARGS(&mDev)) == S_OK)
		{
			featureLevel = lv;
			break;
		}
	}

すべてがだめな状況はめったにないと思いますが、その場合mDevがnullptrになるため、プログラムを終了する処理を追加すれば大丈夫でしょう。

IDXGIFactory6の初期化はこのようにします。

#ifdef _DEBUG
	HRESULT result = CreateDXGIFactory2(DXGI_CREATE_FACTORY_DEBUG, IID_PPV_ARGS(&_dxgiFactory));
#else
	HRESULT result = CreateDXGIFactory1(IID_PPV_ARGS(&_dxgiFactory));
#endif-

デバッグビルドでは問題が発生したときにデバッグ情報を確認できるように、リリースビルドと異なるようにします。

複数のグラフィックボードに対する対応方法は省略します。
気になる方はIDXGIFactory6でアダプター情報を取得する方法を調べてください。

そして、上記の関数でも返されるHRESULT型について少し話します。
DirectX APIはほとんどこの型を返します。

成功するとS_OKを返し、失敗するとそれ以外の値を返します。

レンダリング

さて、画面を描画する準備をしましょう。
DirectX 11では三角形を1つ描くのはそれほど難しくなかったと思いますが、
DirectX 12では生成するオブジェクトの数も多く、コードも長くなった感じがします。
頑張りましょう。

必要なもの

画面を描画するためには基本的に以下の準備物が必要であることを覚えておいてください。
-コマンドリスト
-コマンドキュー
-スワップチェイン
-レンダーターゲット
-ディスクリプタビュー

最初にこれらを見たとき、私は混乱しました。DirectX11ではスワップチェーンとレンダーターゲットがありましたが、他のものは馴染みがなかったからです。
一つずつ見ていきましょう。

コマンドリスト

最終的にはレンダリングを行うためにGPUに命令を出すことになります。
その命令をまとめるオブジェクトです。

DirectX 11ではDeviceContextというものがあり、これで命令を出すと即座に実行されていました。
しかし、DirectX 12ではコマンドリストに命令をまとめて送ることになります。つまり、遅延実行です。

このような遅延実行は実際にはマルチスレッドレンダリングに特化していますが、この記事では単一スレッドでレンダリングできるように作成します。

コマンドリストを作成するコードを見てみましょう。

ID3D12CommandAllocator* mCmdAllocator = nullptr;
ID3D12GraphicsCommandList* mCmdList = nullptr;

result = _dev->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&mCmdAllocator ));
result = _dev->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, mCmdAllocator, nullptr, IID_PPV_ARGS(&mCmdList));

突然コマンドアロケーターは何だ?と思うかもしれません。

GPUの命令メソッドには例えば次のようなものがありますが

DrawInstanced 点や線やポリゴンを描く
DrawIndexedInstanced インデックスを持っている線やポリゴンを描く
ClearRenderTargetView レンダーターゲットをクリア
Close コマンドを閉じる。(必須)

これ以外にもたくさんありますが、これらを集めておく場所がコマンドリストです。

そしてコマンドアロケータはこのコマンドリストの本体です。コマンドの内容を集めておくための実際のメモリ領域と考えればよいでしょう。

実際の領域はコマンドコマンドアロケータ
領域を使用する手段はコマンドリスト
上記のものを利用して集めた命令を実行するのはコマンドキュー

コマンドキュー

コマンドキューオブジェクトは次のように生成します。

ID3D12CommandQueue* mCmdQueue = nullptr;

D3D12_COMMAND_QUEUE_DESC cmdQueueDesc = {};

//タイムアウトなし
cmdQueueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;

//アダプターを1つ使うと0で大丈夫
cmdQueueDesc.NodeMask = 0;

//プライオリティは指定しない
cmdQueueDesc.Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL;

//コマンドリストと合わせる
cmdQueueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
	
result = _dev->CreateCommandQueue(&cmdQueueDesc, IID_PPV_ARGS(&mCmdQueue));

スワップチェーン

今回はスワップチェーンを生成します。
スワップチェーンはダブルバッファリングのためのものです。

ダブルバッファリング

実際に画面に表示するためのシーンを描くにはある程度の時間が必要です。
そうすると画面に描かれる過程が目に見えるようになります。
私たちがゲームをプレイするときにこのような場合を見たことがありませんよね? だからこうなってはいけません。
そのため、シーンを描くためのテクスチャを2枚用意し、レンダリングが終わったシーンをまず画面に表示し、もう一方のテクスチャでレンダリングを行います。これを繰り返すことをダブルバッファリングといいます。こうすることで、人間が感じるには途切れのない画面に見えるようになります。
だいたいのゲームのクオリティ設定画面でバッファリングに関する設定を見ることができます。私たちはダブルバッファリングのみを扱いますが、状況に応じてトリプルバッファリングやそれ以上の数のバッファリングを行う場合もあります。
スワップチェーンは、画面に表示するテクスチャのメモリを2つ以上持ち、画面を切り替える時点で表示するテクスチャの参照先を変更します。
image.png
image.png
image.png

スワップチェーンを作成するコードは次のようになります。

DXGI_SWAP_CHAIN_DESC1 swapchainDesc = {};

swapchainDesc.Width = window_width;
swapchainDesc.Height = window_height;
swapchainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapchainDesc.Stereo = false;  
swapchainDesc.SampleDesc.Count = 1;  
swapchainDesc.SampleDesc.Quality = 0;
swapchainDesc.BufferUsage = DXGI_USAGE_BACK_BUFFER;
swapchainDesc.BufferCount = 2;  //ダブルバッファリングなので2

//バックバッファは伸び縮み可能
swapchainDesc.Scaling = DXGI_SCALING_STRETCH;

swapchainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swapchainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;

//ウインドウモードとフルスクリーンモード切り替え可能
swapchainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;

result = _dxgiFactory->CreateSwapChainForHwnd(_cmdQueue, hwnd, &swapchainDesc, nullptr, nullptr, (IDXGISwapChain1**)&mSwapChain);

次は、レンダーターゲットビューが必要です。

レンダーターゲットビュー

さて、実際にスワップチェインに登録し、画面に表示するシーンを描くテクスチャが必要です。

そのテクスチャをレンダーターゲットと呼び、そのレンダーターゲットを実際に使用できるようにするのがレンダーターゲットビューです。
ここでバッファとビューについて少し話しましょう。

バッファはデータを保管する領域のことを言います。これは3Dプログラミングに限らず、他の分野でもバッファという言葉を使います。ここでも同じです。

わかりやすく言えば、レンダリングのための材料と考えればよいでしょう。

実際に使用されるバッファの例としては

頂点バッファ
インデックスバッファ
コンスタントバッファ
テクスチャバッファ
深度バッファ

があります。

ビューはこのバッファの使用方法を指定するものです。
例えば、テクスチャバッファを作成し、これをシェーダーで使用するリソースとしてだけでなく、レンダターゲットとしても使用する場合があります。このような時、テクスチャバッファを作成し、ビューを通して使用方法を変更して使用します。
これは後で実際に使用するとどのような感じか分かるでしょう。

今回はディスクリプタについて話します。

ディスクリプタはリソースの用途や使用法を説明するデータです。
え?ビューと同じじゃないですか?と思われるかもしれませんが、その通りです。
まだ話していないSamplerというものがありますが、ディスクリプタはサンプラーとビューを包括する概念です。

それでは、ディスクリプタヒープについて話します。

ディスクリプタヒープ

このディスクリプタヒープは確かに複雑ですが、実際に使ってみると「ああ、こういうことか」と理解できるようになります。
このディスクリプタヒープは先ほど話したディスクリプタのメモリ領域と考えてください。
ディスクリプタヒープの中にディスクリプタを置き、実際に使うときはディスクリプタヒープを使って操作することになります。
簡単に言えば、ディスクリプタのコレクションと考えても良いでしょう。

では、早速ディスクリプタヒープを作成してみましょう。

D3D12_DESCRIPTOR_HEAP_DESC heapDesc = {};

heapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
heapDesc.NodeMask = 0;
heapDesc.NumDescriptors = 2;
heapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;

ID3D12DescriptorHeap* rtvHeaps = nullptr;
result = _dev->CreateDescriptorHeap(&heapDesc, IID_PPV_ARGS(&rtvHeaps));

D3D12_DESCRIPTOR_HEAP_DESC について話しましょう。
Typeはどのビューを作成するかに関する列挙子です。
よく使用されるのは以下の通りです。

D3D12_DESCRIPTOR_HEAP_TYPE_RTV レンダーターゲットビュー
D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV 定数バッファ、テクスチャバッファ、コンピュートシェーダー用バッファビュー
D3D12_DESCRIPTOR_HEAP_TYPE_DSV デプスステンシルビュー
NodeMaskは複数のGPUを使用する時に識別するためのビットフラグなので0にします。

NumDescriptorsは作成するディスクリプタの数です。今回はダブルバッファリングのためのレンダーターゲットを作成するので2にします。

Flagsはこのビューに対応する情報をシェーダーで参照するかどうかのフラグです。

今回はダブルバッファリングに使うのでD3D12_DESCRIPTOR_HEAP_FLAG_NONEにしますが、テクスチャバッファビューや定数バッファビューならD3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLEを指定してシェーダーで使用できるようにします。

スワップチェーンのメモリとディスクリプタヒープを接続

これで、スワップチェーンのバッファをレンダーターゲットとして使用できるようにディスクリプタヒープを接続しましょう。

DXGI_SWAP_CHAIN_DESC swcDesc = { };
result = mSWapChain->GetDesc(&swcDesc);

std::vector<ID3D12Resource*> mBackBuffers(swcDesc.BufferCount);

D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = {};
rtvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D;

D3D12_CPU_DESCRIPTOR_HANDLE handle = rtvHeaps->GetCPUDescriptorHandleForHeapStart();

for (int idx = 0; idx < swcDesc.BufferCount; ++idx)
{
	result = _swapChain->GetBuffer(idx, IID_PPV_ARGS(&mBackBuffers[idx]));
	_dev->CreateRenderTargetView(mBackBuffers[idx], &rtvDesc, handle);
	handle.ptr += _dev->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
}

GetDesc()で作ったスワップチェーンの情報を取得できます。

レンダーターゲットビューを作成するためにD3D12_RENDER_TARGET_VIEW_DESCを設定します。

そしてバッファーの数だけレンダーターゲットビューをディスクリプタヒープに生成します。

CreateRenderTargetViewの最初のパラメーターはバッファーです。スワップチェインでGetBufferで取得できます。

2番目のパラメーターは先ほど設定したD3D12_RENDER_TARGET_VIEW_DESC

3番目のパラメーターはディスクリプタヒープ上での一種のアドレスです。

ptrというメンバーを持っており、ここにアドレスが入ります。

GetCPUDescriptorHandleForHeapStartで取得できます。

取得したハンドルは0番目のディスクリプタ領域を指します。

私たちは2つ作成する必要があるので、1番目の領域も知る必要があります。

ハンドルのptrメンバーを一般的なポインタのようにアドレスを移動させることができます。

しかし、ビューの種類によってディスクリプタのサイズが異なるため、GetDescriptorHandleIncrementSizeで必要なサイズを取得します。

最終的に上記のコードのようにすると、2つのレンダーターゲットビューをディスクリプタヒープに作成することができます。
image.png
こんな感じです。

スワップチェーンを動作させる

今度はスワップチェーンを動作させましょう。
今は何かを描いたりするわけではないので、レンダーターゲットを任意の色でクリアする命令だけにしましょう。
現在のバックバッファのインデックスを取得します。

int bbIdx = _swapChain->GetCurrentBackBufferIndex();

得られた値は次のフレームに表示されるバックバッファのインデックスです。
私たちは2つのバッファを使用するので、0か1のどちらかです。
このインデックスのレンダーターゲットビューをレンダーターゲットとして設定する命令をコマンドリストに入れます。

D3D12_CPU_DESCRIPTOR_HANDLE rtvH = rtvHeaps->GetCPUDescriptorHandleForHeapStart();
rtvH.ptr += bbIdx * mDev->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);

mCmdList->OMSetRenderTargets(1, &rtvH, true, nullptr);

レンダーターゲットを指定色でクリアするコマンドを入れます。
今回は黒に設定します。

float clearColor[] = { 0.0f, 0.0f, 0.0f, 1.0f };
mCmdList->ClearRenderTargetView(rtvH, clearColor, 0, nullptr);

コマンドを実行する前に必ずコマンドリストをクローズし、コマンドキューを使用してコマンドを実行すると、コマンドアロケータと一緒にクリアします。

mCmdList->Close();

ID3D12CommandList* cmdlists[] = { mCmdList};
mCmdQueue->ExecuteCommandLists(1, mCmdList);
	
mCmdAllocator->Reset();
mCmdList->Reset(_cmdAllocator, nullptr);

コマンドを実行したら、グリーンバッファーをプレゼントします。

mSwapChain->Present(1, 0);

こうすれば全て終わるかのように思えますが、まだ少しやるべきことがあります。
最初に話したように、DirectX 12は遅延実行です。
上記で作成した命令の実行を命令した直後にすべての命令が完了するわけではありません。
つまり、このままではGPUが画面をすべて描画する前にバックバッファを切り替えてしまうことになります。

これを防ぐために、フェンスを使用して命令がすべて完了するのを待つ処理を追加しましょう。

ID3D12Fence* mFence = nullptr;
UINT64 mFenceVal = 0;

result = mDev->CreateFence(_fenceVal, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence));

ExecuteCommandListsを呼んだ後に次のようにSignalを呼びます。

mCmdQueue->ExecuteCommandLists(1, cmdlists);
mCmdQueue->Signal(mFence, ++mFenceVal);

if (mFence->GetCompletedValue() != mFenceVal)
{
			
}

コマンドキューに命令の実行が完了したら ++mFenceVal の値を返すように指示します。
GetCompletedValueは命令が完了するとSignalに渡された値を返します。
whileを使用してmFenceValとGetCompletedValueの返り値を比較する方法もありますが、私たちはWindowsのWaitForSignalObjectを使用して待つことにします。

ID3D12CommandList* cmdlists[] = { mCmdList };
mCmdQueue->ExecuteCommandLists(1, cmdlists);

mCmdQueue->Signal(mFence, ++mFenceVal);

if (mFence->GetCompletedValue() != mFenceVal)
{
	HANDLE event = CreateEvent(nullptr, false, false, nullptr);

	mFence->SetEventOnCompletion(mFenceVal, event);

	WaitForSingleObject(event, INFINITE);

	CloseHandle(event);
}

イベントを生成し、SetEventOnCompletionで命令実行が完了したらイベントが呼ばれるように設定します。
Windowsのイベントについてよく知らない方は調べてください。

本当に最後です。
リソースの状態変換をGPUに知らせる必要があります。以前に話したように、リソースはさまざまな方法で利用可能だからです。
これを適切に知らせなくても問題がない場合もありますが、予期しない問題を引き起こす可能性があるため、用途に応じて状態変換をGPUに知らせることが望ましいです。
今回はバックバッファに対する状態変換命令を作成します。

D3D12_RESOURCE_BARRIER BarrierDesc = {};

BarrierDesc.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
BarrierDesc.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; 
BarrierDesc.Transition.pResource = mBackBuffers[bbIdx];  
BarrierDesc.Transition.Subresource = 0;
BarrierDesc.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;  
BarrierDesc.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;  

mCmdList->ResourceBarrier(1, &BarrierDesc); 

mCmdList->OMSetRenderTargets(1, &rtvH, true, nullptr);

OMSetRenderTargetsをする前にバックバッファの状態を切り替えます。
元々は画面転換のリソース(D3D12_RESOURCE_STATE_PRESENT)でしたが、レンダーターゲット(D3D12_RESOURCE_STATE_RENDER_TARGET)として使用することを知らせます。
すべての命令が終わり画面を転換する前には、以下のようにまた切り替えます。

BarrierDesc.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
BarrierDesc.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
mCmdList->ResourceBarrier(1, &BarrierDesc);
mCmdList->Close();

画面を指定色でクリアするループコードの全体の内容はこのようになります。

int bbIdx = mSwapChain->GetCurrentBackBufferIndex(); 
D3D12_CPU_DESCRIPTOR_HANDLE rtvH = rtvHeaps->GetCPUDescriptorHandleForHeapStart();
rtvH.ptr += bbIdx * mDev->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);

BarrierDesc.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
BarrierDesc.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
BarrierDesc.Transition.pResource = mBackBuffers[bbIdx];
BarrierDesc.Transition.Subresource = 0;

BarrierDesc.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
BarrierDesc.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;

mCmdList->ResourceBarrier(1, &BarrierDesc); 

mCmdList->OMSetRenderTargets(1, &rtvH, true, nullptr); 

float clearColor[] = { 0.0f, 0.0f, 0.0f, 1.0f };

mCmdList->ClearRenderTargetView(rtvH, clearColor, 0, nullptr);  

BarrierDesc.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
BarrierDesc.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
mCmdList->ResourceBarrier(1, &BarrierDesc); 

mCmdList->Close();  

ID3D12CommandList* cmdlists[] = { mCmdList};
mCmdQueue->ExecuteCommandLists(1, cmdlists); 

mCmdQueue->Signal(mFence, ++mFenceVal); 

if (mFence->GetCompletedValue() != mFenceVal)  
{
	HANDLE event = CreateEvent(nullptr, false, false, nullptr);

	mFence->SetEventOnCompletion(mFenceVal, event);

	WaitForSingleObject(event, INFINITE);

	CloseHandle(event);
}

mCmdAllocator->Reset(); 
mCmdList->Reset(mCmdAllocator, nullptr);

mSwapChain->Present(1, 0);  

ビルドして実行してみると、以下のように画面が黒色にクリアされることを確認できます。
image.png

まとめ

今回は長くなる前にここでまとめます。
何かをレンダリングするための基本的な準備を終えました。
初めて見る概念や用語に混乱するかもしれませんが、後で慣れれば思ったほど難しくありません。
ディスクリプタヒープは引き続き使用するので、慣れてください。

次回はポリゴンをレンダリングし、テクスチャをロードしてポリゴンに貼り付けてみます。

次回

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2