GPU の非同期実行

DirectX12 や Vulkan の長所はここ。

DirectX12で非同期コンピュート もんしょさん
https://sites.google.com/site/monshonosuana/directxno-hanashi-1/directx-147?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1

AMDが Async Shaders を猛烈にアピール。とっくに実装済みの機能が DX12時代の切り札となる!?
https://www.4gamer.net/games/033/G003329/20150330067/

Asynchronous Shaders White Paper
http://amd-dev.wpengine.netdna-cdn.com/wordpress/media/2012/10/Asynchronous-Shaders-White-Paper-FINAL.pdf

DirectX Raytracing HLSL 覚え書き

TraceRay()

引数によって、アクセスできる Shader Table を変えられる。TraceRay() の引数の値が、Hit Group や Miss Shader へアクセスするための Shader Table の計算に使われる。

詳しくは DXR Document の 6章参照。

DispatchRaysDimensions()

DispatchRays() の構造体に指定した width, height, depth の値が返ってくる。

DispatchRaysIndex()

DispatchRaysDimensions() に対する、現在の x, y 位置が返ってくる。

uint3 launchIndex = DispatchRaysIndex();
Output[launchIndex.xy] = result;

などと書いて UAV を書き換えている。

Payload Structure

ユーザー定義の構造体。Payload にアクセスできるのは Any Hit Shader, Closest Hit Shader, Miss Shader のみ。(Ray Generation Shader も?)

Attributes Structure

Any Hit Shader と Closest Hit Shader で読める、HLSL 内で宣言された構造体。ヒットした時の三角形の barycentric (重心) パラメータを持つ。今の所これだけ?

InstanceID()

TLAS で BLAS の頂点データから作ったインスタンスは、任意の InstanceID を設定することが出来る。この InstanceID の値は、Closest Hit Shader などから参照して、インスタンスごとにシェーディングを変えられる。

オブジェクトが3つあり、マテリアルパラメータがそれぞれ違う場合、インスタンスごとの値を定数バッファの配列で定義して、InstanceID で取得する?

ReportHit()

Intersection Shader の中からのみ呼べる。

IgnoreHit()

Any Hit Shader の中からのみ呼べる。

AcceptHitAndEndSerch()

Any Hit Shader の中からのみ呼べる。

DirectX Raytracing API 覚え書き

Acceleration Structure

AS は top-level AS と bottom-lebel AS の2つに別れている。どのような構造になっているかはドライバの実装によるみたいだが、BLAS は実際の頂点バッファを保持し、TLAS は BLAS で定義したオブジェクトのインスタンスと自身の変換行列を保持している。ような実装になっているのかも。

BLAS は AS の基礎となり、TLAS はその上にあるイメージ。BLAS だけだとレイとオブジェクトの交差判定がキツイので最適化のために TLAS が用意されている。TLAS は BLAS で定義したオブジェクトをインスタンス化して持つことができる。

RayTracing Shader

  • Any Hit Shader
    • Alpha Test や Intersection の破棄などに使われる。
  • Callable Shader
  • Closest Hit Shader
    • レイがヒットした箇所の色を求める
    • ラスタライザのピクセルシェーダに似ている
  • Intersection Shader
  • Miss Shader
    • 天球描画やskylightの処理など
    • ほとんどの場合、空のに射出したレイになる?
  • Ray Generation Shader
    • TraceRay() を呼び出し結果を UAV に書き込む

DirectX Raytracing では 6つのシェーダーがサポートされている。ラスタライズの場合は事前に描画時に何のシェーダーを使用するのか決まるが、レイトレースの場合は何のオブジェクトに当たるか分からないため、レンダリングに必要な全てのシェーダーにアクセス出来る必要がある。

Hit Group

レイとジオメトリとの交差判定の計算は、Intersection Shader, Any Hit Shader, Closest Hit Shader に分けれる。この3つのシェーダーは Hit Group と呼ばれて1つにまとめられる。

D3D12_HIT_GROUP_DESC で Any Hit Shader, Closest Hit Shader, Intersection Shader の 3つがまとめられて、StateSubObject に入る。Closest Hit Shader 以外は省略可能。Hit Group には HitGroupExport 引数に名前を指定することで、まとめて一つの名前が付けられる。Shader Table に代入するときに GetShaderIdentifier() で取得するときは、この名前でアクセスする。

Local Root Signature

DXR のシェーダーでは Global と Local の両方が設定できる。Local Root Signature は、Shader Table からデータを取得する。RayGen で使う Local Root Signature と Miss や Hit Group で使う Local Root Signature は別々に定義できる。

Root Signature と Export Association の定義は1対1になる?Export Association を定義するときに、Root Sig の StateSubObject を一緒に渡している。具体的に何をやっているか分からないが、シェーダーのエントリーポイント名とRoot Sig を一緒に渡しているので、個々のシェーダーと Root Sig の生合成のチェックなどのために、Export Association に渡しているのか?そんな感じのイメージで良いのだろうか。。

Raygen からは、Acceleration Structure や Output UAV さえ見えていれば基本的に大丈夫?そのため、シェーディング情報などの Descriptor は、Raygen の Local Root Sig には設定しない。逆に Miss や Hit Group からは、Acceleration Structure や Output UAV は見えなくて良いため、マテリアル情報などの SRV を Local Root Sig に設定する。

Hit Group (Closest Hit Shader) で定数バッファを参照したいとき、Upload Heap で定数バッファを作成して Map-Unmap で値を設定。Buffer::GetGPUVirtualAddress() を、Hit Group の Shader Table に書き込む。

Raytracing Pipeline State

Raytracing Pipeline は、シェーダーコード、Root Signature、パイプライン特性などを一つにバインドした構造になっている。Pipeline State は StateSuboject (D3D12_STATE_SUBOJECT_TYPE) を複数保持している。

  • DXIL_LIBRARY
  • HIT_GROUP (D3D12_HIT_GROUP_DESC)
  • LOCAL_ROOT_SIGNATURE
    • RayGen がアクセスする Root Signature
    • レイトレ出力用の UAV#u0 と Acceleration Structure の SRV#t0
  • EXPORT_ASSOCIATION
    • なにこれ?
    • Root Sig to RGS?
  • LOCAL_ROOT_SIGNATURE
    • Miss と Hit Group がアクセスする Root Signature
  • EXPORT_ASSOCIATION
    • なにこれ?
    • Associate Root Sig to Miss and CHS?
  • RAYTRACING_SHADER_CONFIG
    • Attribute の最大サイズ
    • Payload の最大サイズ
  • EXPORT_ASSOCIATION
    • Associate Shader Config to Miss, CHS, RGS?
  • RAYTRACING_PIPELINE_CONFIG
    • MaxTraceRecursionDepth
    • レイの最大再帰回数(何次レイまで出すか)
  • GLOBAL_ROOT_SIGNATURE

Shader Resource for Raytracing

Raytracing の出力結果は Texture2D の UAV に書き込まれる。また、Acceleration Structure は SRV でアクセスする。Descriptor Heap を個数2で作成して、UAV と SRV で、それぞれ Descriptor を作成しておく。後述する Shader Table に、シェーダーからアクセスできるように Descriptor を設定する。

Shader Table

格納する Entry の数分だけ領域を確保した、Upload Heap のバッファ。そのため、バッファを作った後は、自分で Map-Unmap して、アドレス計算して、各 Entry のメモリー領域にデータをコピーする。1つの Entiry のことを ShaderRecord と言う。ShaderRecord は固定長の任意のサイズにすることが可能で、この大きさは記憶しておいて DispatchRays するときに構造体に渡す。

先に作った Raytracing Pipeline State から、各シェーダーの Shader Identifier を取得して、Shader Table の Entry の先頭に埋め込む。

Ray-gen program のみ、先頭に Shader Identifier を埋め込み、次に Output 用の UAV と Scene 用の SRV の Descriptor を設定する。Heap::GetGPUDescriptorHandleForHeapStart() でアドレスを渡して、書き込む。

TLAS でインスタンスを作成するときに、Shader Table への Offset 値を設定することが出来る (InstanceContributionToHitGroupIndex 厳密にはこれは Offset 値ではなく、かなり複雑な計算が行われる)。これにより、Hit Group 用の Shader Table を複数個作って、それぞれのインスタンスで違う Shader Record にアクセスできる。アクセスする Shader Record を変えたい時はここを設定する。実際に使用する Shader Record を算出する式はかなり複雑。

詳しくは DXR Document の「6 System limits and fixed function behaviors」を参照。Hit group table indexing の算出方法が載っている。

TraceRay() の 第4引数、第5引数の値には要注意。この値によって、参照される Shader Table が変わってくる。

Dispatch Ray

  • RayGenerationShaderRecord
  • MissShaderTable
  • HitGroupTable
  • CallableShaderTable

それぞれに対して ShaderTagle で定義したバッファの Buffer::GetGPUViratulAddress() を startAddress に渡して定義。Raytracing Pipeline State とDispathRayDesc の構造体を CommandBuffer に渡して、DispatchRay をする。

Present

Raytracing の結果は UAV に書き込む。そのため、Output UAV をUnorderedAccess -> CopySource でリソースバリア。Swap Chain のバッファを StatePresent -> CopyDest でリソースバリアして、Output UAV を Swap Chain の領域に CommandBuffer::CopyResource() して、最後に Preset して結果を描画するという手順を踏む。

Direct3D12 CreateCommittedResource

Direct3D12 でのリソースと暗黙のヒープ領域を作成する。リソースに必要となる領域を確保してリソースをヒープにマップする。作成時にリソースの用途に応じて Heap Type を指定する。主に使われるのは Default と Upload の2つ。

通常は 1 Resource に対して、Default と Upload の2つ作り、Upload にリソースをマッピングしてから Upload から Default にコピーして使用する。

DEFAULT heap

GPU が高速に読み書きできる標準のヒープ。CPU からアクセスすることが出来ない(Map できない)ため、Upload Heap から Default Heap に投入される。

UPLOAD heap

CPU で読み取れるため、一般的には Default Heap の初期化や、動的更新が必要なリソースに使用される。GPU からも読み取れるが、速く無いので普通は Default Heap にコピーして使用する。Upload Heap は D3D12_RESOURCE_STATE_GENERIC_READ を使用して作成する必要があり、ここから変更することが出来ない。(そうなの…?Descriptor として使用するときはどうするの?)

Buffer::GetGPUVirtualAddress() を使って、GPU の仮想アドレスが取れる。VertexBufferView などを作るときは、View.BufferLocation にこの GPU Virtual Address を入れて初期化している。

Create Vertex Buffer

Direct3D12 の HelloWorld のサンプルでは頂点データは Upload Heap を使っているがこれは推奨されない。コードの簡略化のために使用しているだけで、通常は頂点データのような静的データは Default Heap を使う。

Create Texture

Default Heap と Upload Heap を2つ作る。Default Heap は StateCopyDest として作成。Upload Heap は StateGenericRead として作成。

d3dx12.h の中に UpdateSubresources() 関数があるから、それを使って、リソースの更新がされる。UpdateSubresources() の中では、まずは Upload Heap (Intermediate Resource) に対して、Map-Unmap してテクスチャのメモリー領域をコピーする。

その後に CommandList::CopyTextureRegion() を使って Upload Heap から Default Heap にリソースをテクスチャがコピーされる。なお Buffer の場合は CommandList::CopyBufferRegion() が呼ばれている。

CopyTextureRegioun() の後に CommandList::ResourceBarrier() を貼り、Default Heap のステート StateCopyDest から StatePixelShaderResource に遷移させて使用する。

後は、Default Heap にコピーしてリソースを使用して、SRV の Descriptor を作ればパイプラインで使用できる。

Create Constant Buffer

定数バッファは Upload Heap として作成 ( View, Project 行列など )。State は GenericRead。作成後、buffer->GetGPUVirtualAddress() を Constant Buffer Desc の構造体の desc.BufferLocation に入れて CBV を作成。なお Size は 256 byte でアラインされていないといけない。

Hello World のサンプルでは、Constant Buffer の Upload Heap の領域を Map したあとに、Unmap はしていない。OnUpdate() でこの初期化時にマップされたアドレスを使って、定数バッファの更新を毎フレーム行なっている。リソースさえ生きていれば、マップされたアドレスを保持し続けていても問題無い。

Direct3D12 覚え書き

Command Queue の作成

CommandQueue は Compute Shader タスクがあれば分割。作成時の構造体は通常の Queue は TYPE_DIRECT だが、Compute は TYPE_COMPUTE にする。Swap Chain に渡す Command Queue は Direct。Command Queue が分かれるので、作成する Command Allocator や Command List もそれぞれ、Direct 用と Compute 用で分割。

Descriptor Heap

Descriptor Heap は最低限、RTV, DSV, CBV/SRV/UAV の3つで分けて作成。Descriptor Handle 用のサイズも個々に取得。もちろん Descriptor 1個だけであればサイズ取得は不要。

Fence で同期

Compute タスクが終わった後に、Graphics タスクを行いたいような場合は、Fence を2つ作り、Compute タスクの終了を CommandQueue::Wait() で待つ。

Root Signature と Root Arguments の設定

Graphics タスクの Root Signature は CommandList::SetGraphicsRootSignature() で設定し、Compute タスクの Root Signature は CommandList::SetComputeRootSignature() と別れて設定する。

ExecuteIndirect

DirectX11 までは Draw と Dispatch で Indirect 命令が別れていたが、DirectX12 では統合された? CommandSignature というのを事前に作っておく必要がある。

Direct3D12 Resource Binding

涙出るぐらい難しいが、数時間 MS doc と格闘していたら、ドライアイと目の炎症を起こして涙すら出なくなりました。ただ、最近になって MS doc は表示テーマが切り替えられるようになっていることを知り、もう少し早く Dark モードの存在に気が付いていれば今よりはこの痛みがマシだったろうになと後悔です。忙しすぎて眼下にも行けていなく毎日が大変です。

ついでに DX12 は CD3DX12 というヘルパークラスでラップされているのも便利ではありますが理解の妨げになって辛いです。

Root Signature

Root Signature は、何のリソースがレンダリングパイプラインのどのレジスタースロット位置にバインド(リンク) されるのかを定義する。

シェーダーリソースのレイアウト定義表。シェーダーに入力するリソースを決めるため、使用するシェーダー側と定義が同じでなければいけない。Root Signature とシェーダーの整合性は PSO 作成時に検証される。

  • CommandList::SetGraphicsRootSignature()

で、コマンドリストに Root Signature を設定する。PSO 作成時には必ず RootSignature を設定しないといけない。仮に Resource が必要無い描画であっても、空の RootSignature を作って設定しておく。

Root Signature には Root Constants (インライン化された定数) と Root Descriptors (インライン化された Descriptor) と Descriptor Tables の3種類を含めることができる。

Root Parameter

Root Signature の1つのエントリーの事。実際の Root Parameter の値は Root Argument と呼ばれる。Root Argument が変わるとシェーダーが読み取るデータが変わる。

  • CommandList::SetGraphicsRootDescriptorTable()

第1引数には、この Root Parameter のインデックスを指定する。第2引数は SRV などの Descriptor Handle を渡す。この Root Parameter に指定する Root Argment を変えることによるシェーダーが読み取るデータが変わる。

Root Descriptor

Root Signature にインラインで指定できる Descriptor。アクセス頻度が高いものは Root Descriptor として指定すると効率的。CBV, UAV, SRV のみ指定できる。

Descriptor

SRV, CBV, UAV, Sampler は、直接パイプラインにバインドされずに、Descriptor というリソースの情報を含んだオブジェクトを経由して参照される。

Descriptor Table

複数の Descriptor をグループ化したもの。Descriptor Heap から領域を確保される。描画時に使用する Descriptor Heaps と DescriptorTable を CommandList に登録する。

Descriptor Heap

Descriptor Table のバッファ領域。レンダリング時に使用する Descriptor Heap を CommandList::SetDescriptorHeaps() で指定した後に、CommandList::SetGraphicsRootDescriptorTable() する必要がある。ただ、RTV の場合はしなくて大丈夫?

Root Signature の作成

たぶん個々の説明を読むよりドキュメントのコードを見た方がいい。

  • CD3DX12_DESCRIPTOR_RANGE1 DescRange[6];

下記の Root Parameter で Descriptor Table を使用するに、ここで Descriptor Table の範囲を定義している。この DescRange を InitAsDescriptorTable に渡す事で Descriptor Table を定義している。


> DescRange[0].Init(D3D12_DESCRIPTOR_RANGE_SRV,6,2); // t2-t7
SRV を slot:b2 開始で 6個定義。つまり t2-t7 までの Range で使用する。

  • CD3DX12_ROOT_PARAMETER1 RP[7];

Root Parameter を 7個定義。このヘルパークラスで、Root Parameter を個々に設定できる。

  • InitAsDescriptorTable (上で作った Range を指定する)
  • InitAsConstants
  • InitAsShaderResourceView (Descriptor 単体。これが Root Descriptor?)
  • InitAsUnorderedAccessView (Descriptor 単体)

CommandList::SetGraphicsRootDescriptorTable() などの関数で指定するのは、この配列の Root Parameter Index 値になる。

Direct3D12 覚え書き

DXGI とは何か

https://docs.microsoft.com/en-us/windows/desktop/direct3ddxgi/dx-graphics-dxgi
Microsoft DirectX Graphics Infrastructure (DXGI) とはグラフィックスアダプター(GPU)の列挙、

デバイスの作成

SwapChain の作成

// Describe and create the swap chain.
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
swapChainDesc.BufferCount = FrameCount;
swapChainDesc.Width = m_width;
swapChainDesc.Height = m_height;
swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferUsage =DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.SwapEffect =DXGI_SWAP_EFFECT_FLIP_DISCARD;
swapChainDesc.SampleDesc.Count = 1;

ComPtr<IDXGISwapChain1> swapChain;
ThrowIfFailed(factory->CreateSwapChainForHwnd(
    m_commandQueue.Get(),        // Swap chain needs the queue so that it can force a flush on it.
    Win32Application::GetHwnd(),
    &swapChainDesc,
    nullptr,
    nullptr,
    &swapChain
    ));

CreateSwapChainForHwnd() の中に「SwapChain は強制的にフラッシュするために、Command Queue を必要とする」というコメントがあります。Window のリサイズ時とか、デバイスに変更が入った時とか?何かしらシステムから現在のコマンドへのフラッシュが必要なタイミングがあるのかな?

Command Queue の作成

// Describe and create the command queue.
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
m_device->CreateCommandQueue(&queueDesc,IID_PPV_ARGS(&m_commandQueue));

特筆するような指定はなさそう? Flags と Type ぐらい。

Command List の作成

// Create the command list.
ThrowIfFailed(m_device->CreateCommandList(0,D3D12_COMMAND_LIST_TYPE_DIRECT,m_commandAllocator.Get(), nullptr, IID_PPV_ARGS(m_commandList)));

// Command lists are created in the recordingstate, but there is nothing
// to record yet. The main loop expects it to beclosed, so close it now.
ThrowIfFailed(m_commandList->Close());

サンプルだと CreateCommandList した直後に、Close() している。コマンドリストは記録状態で作成されるが、作られた直後では記録するものが無いため閉じている。Command List は記録が終わった場合と、記録するものが無い場合などは Close() を呼び出して閉じておく必要がある。

MS ドキュメントには「記録中にオペレーティングシステムのメモリが不足した場合は E_OUTOFMEMORY を返す」とあるが、実際に E_OUTOFMEMORY メモリ不足が返されるのは、Close() が呼び出された時になるのか?どのコマンドによって E_OUTOFMEMORY が返されるか判別する方法はあるのかな?

Command List Type

コマンドリストにはタイプが存在している。
https://docs.microsoft.com/en-us/windows/desktop/api/d3d12/ne-d3d12-d3d12_command_list_type

DIRECT, BUNDLE, COMPUTE, COPY など。通常のグラフィックス用途に使用するコマンドリストと、Compute Shader で使用するコマンドリストなどは分ける必要がある? Command Allocator と Command List の両方に Type を指定できるが、両方は同じにしないといけないのかな。

Fence の作成

// Create synchronization objects.
ThrowIfFailed(m_device->CreateFence(0,D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_fence)));
m_fenceValue = 1;

// Create an event handle to use for framesynchronization.
m_fenceEvent = CreateEvent(nullptr, FALSE, FALSE,nullptr);
if (m_fenceEvent == nullptr)
{
    ThrowIfFailed(HRESULT_FROM_WIN32(GetLastError()));
}

DirectX12 のサンプルだと m_fence(ID3D12Fence) と m_fenceValue(UINT64) と m_fenceEvent(HANDLE) の 3つのオブジェクトを使って同期を管理。

同期待ちに関しては、CreateEvent() と WaitForSignalObject() という昔からある WinAPI を使って処理している。

キーワードとして、下記「イベントオブジェクト」「イベントオブジェクトの使用」「プロセス間通信」などの MSDN を参照

DirectX12 では使われていないが、HANDLE は WinAPI の SetEvent などで signaled に移行できる。今回、後述するが、

m_fence->SetEventOnCompletion(fence, m_fenceEvent)

という、ID3D12Fence::SetEventOnCompletion 関数によって、m_fenceEvent を signaled に移行させている。Fence には値を書き込むことができ、Fence に書き込んだ値によって同期を取っている。

CreateEvent()

CreateEvent() は m_fenceEvent の HANDLE を生成します。

WaitForSingleObject()

指定したオブジェクト(HANDLE)の状態を確認します。オブジェクトのステートは nonsignaled と signaled の2つあり、signaled になるかタイムアウトするまで、そのスレッドが待機する。

https://docs.microsoft.com/en-us/windows/desktop/direct3d12/memory-management
DirectX12 は、メモリ管理と同期を自分で管理しなければいけない。ここで言うメモリ管理とは、CPU から GPU へのアップロードと GPU から CPU へのリードバック。

Command Queue の完了を待つ

App::WaitForPreviousFrame() アプリケーション側で実装している、前フレームの完了(同期)を待っている処理。この処理は、GPU 効率の良いやり方ではないみたい。

const UINT64 fence = m_fenceValue;
ThrowIfFailed(m_commandQueue->Signal(m_fence.Get(), fence));
m_fenceValue++;

// Wait until the previous frame is finished.
if (m_fence->GetCompletedValue() < fence)
{
    ThrowIfFailed(m_fence->SetEventOnCompletion(fence, m_fenceEvent));
    WaitForSingleObject(m_fenceEvent, INFINITE);
}

重要なのが、下記関数の組み合わせ。シグナルをコマンドキューに追加し、コマンドキューの完了を待っている。

  • ID3D12CommandQueue::Signal(ID3D12Fence *pFence, UINT64 Value)
    • GPU から Fence に対して、指定した値に更新することができる
    • つまり Command Queue の完了を待つことができる
    • CPU から Fence に値を設定したい場合は ID3D12Fence::Signal を使う
  • ID3D12Fence::GetCompletedValue()
    • Fence の現在の値を取得する
  • ID3D12Fence::SetEventOnCompletion(UINT64 Value, HANDLE hEvent)
    • Fence が特定の値に達したときに発生させるイベントを指定します。

この一連の処理は、まず CommandQueue が完了後に m_fence(ID3D12Fence) が fence(UINT64) の値に遷移するように Signal() を設定している。直後の GetCompletedValue() が返す値は、まだ前フレームの値のため、この条件分岐は成立して中に入る。if 内の SetEventOnCompletion() は描画完了後に m_fence(ID3D12Fence) の値が fence(UINT64) に達したら、m_fenceEvent(HANDLE) の値を signaled に設定する。

全てのコマンドが処理されて、m_fence(ID3D12Fence) が fence(UINT64) に達するまで WaitForSingleObject() でスレッドが待機する。これが一連の処理。難しい。

Populate CommandList

m_commandAllocator->Reset();
m_commandList->Reset(m_commandAllocator.Get(), m_pipelineState.Get());
m_commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_renderTargets[m_frameIndex].Get(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));

D3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_rtvHeap->GetCPUDescriptorHandleForHeapStart(), m_frameIndex, m_rtvDescriptorSize);

const float clearColor[] = { 0.0f, 0.2f, 0.4f, 1.0f };
m_commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);

m_commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_renderTargets[m_frameIndex].Get(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));
m_commandList->Close();

Populate CommandList だと、コマンドリストに投入する。Poplate Data だと、(DBなどに)データを投入するという意味で使われたりするみたいです。

m_commandAllocator->Reset();

// Command list allocators can only be reset when the associated 
// command lists have finished execution on the GPU; apps should use 
// fences to determine GPU execution progress.
m_commandAllocator->Reset();

コマンドリストアロケータは、関連するコマンドの実行が終了した時にだけリセットすることができる。アプリケーションは、フェンスを使用して GPU の実行状況を判定する必要がある。

この関数はコマンドリストアロケータに関連づけられたメモリを再利用することを示します。

m_commandList->Reset()

// However, when ExecuteCommandList() is called on a particular command 
// list, that command list can then be reset at any time and must be before 
// re-recording.
m_commandList->Reset(m_commandAllocator.Get(), m_pipelineState.Get());

コマンドリストを初期状態にリセットします。リセット(再生成)するための、Command Allocator を指定する。

ResourceBarrier 1

// Indicate that the back buffer will be used as a render target.
m_commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_renderTargets[m_frameIndex].Get(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));

バックバッファーに設定されていたメモリーを、レンダーターゲットとして使用することを指定する

ResourceBarrier 2

// Indicate that the back buffer will now be used to present.
m_commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_renderTargets[m_frameIndex].Get(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));

レンダーターゲットとして設定されていたメモリーを Present として使用するように設定する。

Execute CommandList

// Execute the command list.
ID3D12CommandList* ppCommandLists[] = { m_commandList.Get() };
m_commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);

コマンドリストに記録したコマンドをサブミットします。

Direct3D12 Synchronization

Fence

単純なアプリであれば、CPU-GPU 間の同期を取るだけなので、Fence は1つあれば十分か。D3D12Fence と HANDLE と uint32_t の変数をセットで使用する。

複数の Queue を作ったときの同期も Fence で行う。

ResourceBarrier

リソースの状態を管理する。例えば RenderTarget として使っていたリソースを、ShaderResoruce として使うような場合に、どのような順序でタスクを実行するか(この場合だと先に ShaderResource タスクが実行されると結果がおかしくなるため) ResourceBarrier を使って状態が変わる事をドライバに通知する。

CommandList->ResourceBarrier() に設定する。該当のリソースが、CommandList に積んだ後に、StateBefore から StateAfter に指定した状態として使うことを明示する。

SwapChain なら、StatePresent -> StateRenderTarget として、ResoruceBarrier を設定して、レンダーターゲットに指定する。描画コマンドが一通り終わったら、最後に StateRenderTarget -> StatePresent の ResourceBarrier を積み、Queue を Submit (ExecuteCommandLists) して Present できる。