BaseDx12(Dx12研究とフレームワーク)

【第1部】シンプルバージョン編

1105.シンプルな三角形の描画(5)

 では、前項に続きましてGameDevice::LoadPipeline()関数の中身を解説したいと思います。

ディスクリプタヒープ

 ディスクリプタヒープはGPUとやり取りをする窓口のようなものと考えられます。
 で、その窓口はいくつか種類があります。
*レンダーターゲットビュー用の窓口(RTV)
*デプスステンシルビュー用の窓口(DSV)
*サンプラー用の窓口(SAMPLER)
*コンスタントバッファ、シェーダリソース、順序指定されないアクセスビュー用の窓口(CBV_SRV_UAV)
 以下のような概念です。

 

図1105a

 

 このサンプルではRTV用しか使いません。RTVはフレームの数だけ作成します。このサンプルでは2つです。
 サンプラー用CBV_SRV_UAV用は次のサンプルで説明します。
 RTV用のディスクリプタヒープフレーム数だけ作成します。以下の様な記述になります。
    // レンダリングターゲットビュー
    m_rtvHeap = DescriptorHeap::CreateRtvHeap(m_frameCount);
    m_rtvDescriptorHandleIncrementSize 
    = GetID3D12Device()->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
 このうち赤くなっている部分は、以下のコードを実行します。
    namespace DescriptorHeap {
        static inline ComPtr<ID3D12DescriptorHeap> CreateDirect(const D3D12_DESCRIPTOR_HEAP_DESC& desc) {
            auto device = App::GetID3D12Device();
            ComPtr<ID3D12DescriptorHeap> ret;
            ThrowIfFailed(device->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&ret)),
                L"デスクプリタヒープの作成に失敗しました",
                L"device->CreateDescriptorHeap()",
                L"DescriptorHeap::CreateDirect()"
            );
            return ret;
        }
        static inline ComPtr<ID3D12DescriptorHeap> CreateRtvHeap(UINT frameCount) {
            //Rtvデスクプリタヒープ
            D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {};
            rtvHeapDesc.NumDescriptors = frameCount;
            rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
            rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
            return CreateDirect(rtvHeapDesc);
        }
//中略
    }
 赤くなっている部分が呼び出される関数です。
 ここではD3D12_DESCRIPTOR_HEAP_DESC構造体を設定します。
            rtvHeapDesc.NumDescriptors = frameCount;
 は、フレーム数です。このサンプルでは2ですね。
            rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
 は種類です。RTV用なのでD3D12_DESCRIPTOR_HEAP_TYPE_RTVとなります。
            rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
 ここでrtvHeapDesc.Flagsは、シェーダーにデータを渡す場合はD3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLEになります。
 RTVは、シェーダーに渡すわけではないのでD3D12_DESCRIPTOR_HEAP_FLAG_NONEになります。
 このあと
            return CreateDirect(rtvHeapDesc);
 とCreateDirect()関数D3D12_DESCRIPTOR_HEAP_DESC構造体を渡します。
 CreateDirect()関数ではD3D12_DESCRIPTOR_HEAP_DESC構造体をもとに、RTV用ディスクリプタヒープを作成します。
 作り出すのはID3D12DescriptorHeapインターフェイスです。この時デバイスが必要なのでAppクラスから取り出します。
            auto device = App::GetID3D12Device();
            ComPtr<ID3D12DescriptorHeap> ret;
            ThrowIfFailed(device->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&ret)),
                L"デスクプリタヒープの作成に失敗しました",
                L"device->CreateDescriptorHeap()",
                L"DescriptorHeap::CreateDirect()"
            );
            return ret;
 なぜAppクラスから参照できるかというと、1101.シンプルな三角形の描画(1)で紹介した
    void Scene::OnInit() {
        ResetActiveBaseDevice<GameDevice>(2);
    }
 で、ResetActiveBaseDevice()関数が、GameDeviceクラスのインスタンスをAppクラスに渡しているからです。

 このようにして作成されたRTV用ディスクリプタヒープ
    m_rtvHeap = DescriptorHeap::CreateRtvHeap(m_frameCount);
 とm_rtvHeapに渡されるわけです。
 このあと
    m_rtvDescriptorHandleIncrementSize 
    = GetID3D12Device()->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
 で、そのディスクリプタヒープの大きさを取得します。RTV用ディスクリプタヒープは後ほど、複数のフレーム(すなわち複数のレンダーターゲット)と組み合わされるので、ディスクリプタヒープの大きさが必要です。

RTVとコマンドアロケータ

 ディスクリプタヒープの作成が追わったら、複数のRTV用ディスクリプタヒープレンダーターゲットビューを結びつけます。
 以下がその内容です
    // RTVとコマンドアロケータ
    CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_rtvHeap->GetCPUDescriptorHandleForHeapStart());
    for (UINT n = 0; n < m_frameCount; n++)
    {
        ThrowIfFailed(m_swapChain->GetBuffer(n, IID_PPV_ARGS(&m_renderTargets[n])));
        m_device->CreateRenderTargetView(m_renderTargets[n].Get(), nullptr, rtvHandle);
        rtvHandle.Offset(1, m_rtvDescriptorHandleIncrementSize);
        //コマンドアロケータ
        m_commandAllocators[n] = CommandAllocator::CreateDefault();
    }
 ここではレンダーターゲットビューのほかにコマンドアロケータも作成します。
 コマンドアロケーターは、コマンドリストを作成するためのユーティリティと考えることができます。アロケーターという名前がついているのは、領域確保するユーティリティだからです。コマンドアロケーターもフレーム数だけ必要なので、一緒に作成します。
 レンダーターゲットビューディスクリプタヒープを結びつけるには、ハンドルというのを作成しなければいけません。
    CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_rtvHeap->GetCPUDescriptorHandleForHeapStart());
 で、m_rtvHeapのCPU側の先頭位置ハンドル(rtvHandle)を作成します。
 続いてループに入るわけですが
    for (UINT n = 0; n < m_frameCount; n++)
 で、フレーム数だけ(ここでは2回)ループします。ループ内では
        ThrowIfFailed(m_swapChain->GetBuffer(n, IID_PPV_ARGS(&m_renderTargets[n])));
 で、スワップチェーンからn番目のバッファm_renderTargets[n番目]に取得します。nは、最初は0そして、2回目のループで1になりますね。
 その後
        m_device->CreateRenderTargetView(m_renderTargets[n].Get(), nullptr, rtvHandle);
 で、m_renderTargets[n番目]rtvHandleを結びつけてビューを作成します。これがRTVです。
 そして、最初のフレームのRTVを取得したので
        rtvHandle.Offset(1, m_rtvDescriptorHandleIncrementSize);
 でハンドルをm_rtvDescriptorHandleIncrementSize分だけ後ろにずらします。
 ですので次のループではrtvHandleは、スタート位置からm_rtvDescriptorHandleIncrementSizeだけ後ろになるので、2つ目のフレームのRTVを作成することができます。
 ループ内では、一緒にコマンドアロケータも作成します。コマンドアロケータのm_commandAllocators変数もフレーム数の配列にあっていますので、
        //コマンドアロケータ
        m_commandAllocators[n] = CommandAllocator::CreateDefault();
 と書けます。
 ここで呼びだしているCommandAllocator::CreateDefault()関数は以下です。
    namespace CommandAllocator {
        static inline  ComPtr<ID3D12CommandAllocator> CreateDefault() {
            //デバイスの取得
            auto device = App::GetID3D12Device();
            ComPtr<ID3D12CommandAllocator> allocator;
            ThrowIfFailed(
                device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT,IID_PPV_ARGS(&allocator)),
                L"コマンドアロケータの作成に失敗しました",
                L"device->CreateCommandAllocator()",
                L"CommandAllocator::CreateDefault()"
            );
            return allocator;
        }
    }
 デバイスを取り出し、そのメンバ関数CreateCommandAllocator()関数で、コマンドアロケータをallocatorに取得し、それをリターンします。

 ここまでの処理でGameDevice::LoadPipeline()の処理は終わりです。この関数は、GPU全体にかかわる初期化と考えられます。
 次項では各描画されるオブジェクトに関する初期化である、GameDevice::LoadAssets()関数の解説を行います。