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

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

1106.シンプルな三角形の描画(6)

 この項から個別のオブジェクトの初期化関数であるGameDevice::LoadAssets()に入ります。

ルートシグネチャー

 まず、ルートシグネチャーというインターフェイスを作成します。
 ルートシグネチャー(直訳すればルート署名)は、シェーダーに何を渡すかを定義します。
 シェーダーは以下の種類があります。(Shader Model 5の場合)
*頂点シェーダ
*ピクセルシェーダ
*ジオメトリシェーダ
*ハルシェーダ
*ドメインシェーダ
*計算シェーダ
 これらのシェーダを使って、パイプラインに命令を流す場合、どのシェーダにどのような方法でどのデータを渡すのかを、細かく定義するのがルートシグネチャーと言えます。
 ここで計算シェーダは、描画するためのシェーダではなくGPUを使った計算をするためのものなので、ちょっとわきに置いておきます。
 しかしながら、今回のサンプルではシェーダに渡すのはメッシュの情報のみなので、一番シンプルなルートシグネチャーを作成します。ですのでルートシグネチャーに対する細かい説明は、次のサンプル以降になります。
 GameDevice::LoadAssets()に記述がある、ルートシグネチャーの初期化は以下になります。
        //一番シンプル
        m_rootSignature = RootSignature::CreateSimple();
 そしてその内容は以下になります。赤くなっている部分が呼ばれる関数です。
namespace RootSignature {
    static inline ComPtr<ID3D12RootSignature> CreateDirect(const CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC& desc) {
        auto device = App::GetID3D12Device();
        D3D12_FEATURE_DATA_ROOT_SIGNATURE featureData = {};
        featureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_1;
        if (FAILED(device->CheckFeatureSupport(D3D12_FEATURE_ROOT_SIGNATURE, &featureData, sizeof(featureData))))
        {
            featureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_0;
        }
        ComPtr<ID3DBlob> signature;
        ComPtr<ID3DBlob> error;
        ComPtr<ID3D12RootSignature> ret;
        ThrowIfFailed(D3DX12SerializeVersionedRootSignature(&desc, featureData.HighestVersion, &signature, &error),
            L"ルートシグネチャのシリアライズに失敗しました",
            L"D3DX12SerializeVersionedRootSignature(&rootSignatureDesc, featureData.HighestVersion, &signature, &error)",
            L"RootSignature::CreateDirect()"
            );
        ThrowIfFailed(
            device->CreateRootSignature(0, signature->GetBufferPointer(),
                signature->GetBufferSize(), IID_PPV_ARGS(&ret)),
            L"ルートシグネチャの作成に失敗しました",
            L"device->CreateRootSignature()",
            L"RootSignature::CreateDirect()"
            );
        return ret;
    }
    //一番シンプルなルートシグネチャ
    static inline ComPtr<ID3D12RootSignature> CreateSimple() {
        CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC rootSignatureDesc;
        rootSignatureDesc.Init_1_1(
            0, 
            nullptr, 
            0, 
            nullptr, 
            D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT
        );
        return CreateDirect(rootSignatureDesc);
    }
//中略
}
 ここではCD3DX12_VERSIONED_ROOT_SIGNATURE_DESC構造体を設定します。この構造体はD3D12_VERSIONED_ROOT_SIGNATURE_DESC構造体を継承した、ユーティリティクラスです。ライブラリ中のd3dx12.hに記載されています。
 この構造体にはInit_1_1というメンバ関数があり、パラメータは以下です。
inline void Init_1_1(
    UINT numParameters, //パラメータの数
    _In_reads_opt_(numParameters) const D3D12_ROOT_PARAMETER1* _pParameters, //パラメータのポインタ
    UINT numStaticSamplers = 0, //スタティックサンプラーの数
    _In_reads_opt_(numStaticSamplers) const D3D12_STATIC_SAMPLER_DESC* _pStaticSamplers = nullptr,  
    //スタティックサンプラーのポインタ
    D3D12_ROOT_SIGNATURE_FLAGS flags = D3D12_ROOT_SIGNATURE_FLAG_NONE //フラグ
 入力されるパラメータをを見ますと
    D3D12_ROOT_SIGNATURE_FLAGS flags = D3D12_ROOT_SIGNATURE_FLAG_NONE
 だけが、D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUTになっているのがわかります。
 このパラメータはインプットレイアウトがパイプラインに入力されることを意味します。
 この後CreateSimple()関数CreateDirect()関数を呼び出すわけですが、この関数は、他のタイプのルートシグネチャ作成と共有する関数です。

ルートシグネチャーはどう作成すべきか

 ここでちょっとわき道にそれますが、ルートシグネチャに関する、MS社のガイドブックには、日本語翻訳として


 複数のパイプライン状態オブジェクト (PSO) グループで同じルート署名を共有している状態が理想的です。 パイプラインでルート署名を設定したら、それによって定義されるすべてのバインド (記述子テーブル、記述子、定数) を、バンドルへの継承も含めて個別に設定または変更できます。
 アプリでは、必要な記述子テーブルの数と、ルート署名に必要なインライン記述子の数 (より多くのスペースを使用するが間接参照を解消) やインライン定数 (間接参照なし) の数の間で独自にバランスを取ることができます。
 アプリケーションでは、ルート署名の使用をできるだけ控えて、ヒープや記述子ヒープなどのアプリケーションが制御するメモリを利用してバルク データを表すようにする必要があります。


 という記述があります。
https://docs.microsoft.com/ja-jp/windows/win32/direct3d12/root-signatures-overview
 この記述は、非常に重要で、ようは、ルートシグネチャーはあまりたくさんの種類を作るなということです。
 ゲームにおける各オブジェクトの描画方法はまちまちです。3Dのリアルなオブジェクトもあれば、ライフや残り時間を指すインターフェイスもあります。そして、エフェクトもあります。
 それらを一括して管理する場合、リアルな3D用のルートシグネチャー、インターフェイス用のルートシグネチャー、エフェクト用のルートシグネチャーと別に作るのではなく、それらを包括するルートシグネチャーを最小限の数だけ作成しなさい、ということです。
 ここで、MS社のドキュメントが一種類しか作るなと言っているわけではありません。ルート署名の使用をできるだけ控えてというので、ゲーム中に、複数のルートシグネチャーによる描画があってもよいということです。ただその数はあんまり増やさないように、ということです。
 ですので、このサンプルでは一番シンプルなルートシグネチャーを紹介してますが、これを、実際のゲームの一部として使うのは意味がありません。フレームワーク(つまり、複数タイプのゲーム制作に耐えられるエンジン)を作成するためには、もっといろんなことが可能なルートシグネチャーを定義する必要があります。
 これはBaseDx12フルバージョンにおける、ルートシグネチャーにつながります。
 BaseDx12フルバージョンは、2種類のルートシグネチャーしか定義しません。それは2Dオンリー2D3D混在かの2種類です。
 しかしBaseDx12シンプルバージョンでは、ルートシグネチャはコンテンツ側に置かれます。
 BaseDx12シンプルバージョンをベースにフレームワークを作成するには、オリジナルなルートシグネチャをいくつか作成してライブラリ化することで、よりオリジナルな表現を実装することが可能です。

 ルートシグネチャの説明はこのサンプルではこのくらいにして、次に移ります。