TOP > PROGRAM

はじめてのメッシュシェーダ

1 はじめに…

そろそろメッシュシェーダを用いたアセットワークフローに変更したいなと思ったので,やってみます。

はじめてのメッシュシェーダ
図 1: はじめてのメッシュシェーダ

2 開発環境

手元では,以下の環境で行っています。

DirectX12 Ultimateに対応したグラフィックスドライバー,Windows 10 20H1以降の環境でないと動作確認できないと思うので注意してください。

3 メッシュシェーダとは?

ちょっと大きな規模のゲームを作ると,頂点シェーダがネックになることがあったりします。なぜかというとGPUが巨大になってきていて,入力アセンブラハードウェアはDrawをパイプラインにちょっとずつしか流せないとか,プリミティブアセンブラからの出力レートをフルに保つのが難しいとか,力を上手く使いきれるようにするのが困難になってきているという背景があります。
 近年ではGPU駆動レンダリング[Harr 2015; Mishima 2016]が注目を集めています。その名の通り,GPU上で描画する・しないを判定してGPU上でドローコールを発行するという手法です。ただ,この際に問題があります。頂点シェーダが遅いということです。そこで,ピクセル描画に寄与しないポリゴンを頂点シェーダが実行される前にあらかじめ取り除いておけば,頂点シェーダの負荷が軽減できるはずです。これを実現するために,コンピュートシェーダ上でカリングを行う手法というのが提案されています[Wihlidal 2018]。また,一方でDirectX10以降,ジオメトリシェーダ・ドメインシェーダ・ハルシェーダの追加によりグラフィックスパイプラインは複雑化の一途を辿っています。頂点シェーダも遅いし,他のシェーダを実行すると当然ながら処理が増えるのでその分遅くなります。これまでのグラフィックスパイプラインをこのまま使い続けるのは無理があるように思えます。
そこで,登場するのがメッシュシェーダ(Mesh Shader)による新しいグラフィックスパイプラインです。入力アセンブラはDrawをパイプラインにちょっとずつ流せない問題等があるため廃止され,テッセレータもありません。コンピュートシェーダのように演算が行えます。メッシュシェーダと共に増幅シェーダ(Amplification Shader)も新たに導入されます。増幅シェーダはメッシュシェーダの前に実行され,その名の通りメッシュシェーダの起動を増やすことができます。これによりジオメトリシェーダの様なプリミティブの増減が可能にできます。また,コンピュートシェーダのようにスレッド単位で実行できるので,カリング実装もメッシュシェーダ内で完結することができ,追加のバッファ等が不要になるといったメリットなどもあります。複雑だったシェーダステージも,増幅シェーダ→メッシュシェーダ→ピクセルシェーダの3つになり非常にスッキリします。これからは,メッシュシェーダが主流になっていくことになるでしょう。

4 ポリゴン描画

さて,メッシュシェーダについて勉強していきましょう。今回は,いきなりメッシュレットなどは使わずに1枚の三角形を描画するという初歩的なプログラムを組み,初期化フローをきちんと抑えることをきっちり学びたいと思います。まぁ、ポリゴン1枚描くのにメッシュシェーダは適さないのですが,使い方を覚えるのが主目的なので,そこはグッと目をつぶりたいと思います。
まずは,描画に使用するシェーダを用意します。手元のVisual Studio 2019だとシェーダコンパイルがメッシュシェーダ対応されていなかったので,メッシュシェーダコンパイルに対応したカスタムビルドルールを作ってみました。

https://github.com/ProjectAsura/dxc_rule

上記のサイトからdxc.props, dxc.targets, dxc.xmlの3ファイルを取得して,プロジェクトファイルと同じディレクトリに配置してください。配置したらソリューションエクスプローラー上から「ビルドの依存順序」>「ビルドのカスタマイズ」を選択し,「既存ファイルの検索」を押して,ファイルダイアログからdxc.targetsを選択してください。選択すると一覧にdxcが現れるようになるので,チェックボックスにチェックを入れてOKボタンを押しダイアログを閉じてください。カスタムビルドルールを使うのが嫌な人は,バッチファイルとかつかって頑張ってコンパイルとかしてください。
バイナリファイルをロードするのが面倒なので,今回は

-Fh %(ProjectDir)..\res\shaders\Compiled\%(Filename).inc -Vn %(Filename)

のオプションを付けて,ヘッダファイルで出力されるようにしてします。
 用意するメッシュシェーダですが、下記になります。

///////////////////////////////////////////////////////////////////////////////
// VertexInput structure
///////////////////////////////////////////////////////////////////////////////
struct VertexInput
{
    float3 Position;    // 頂点座標.
    float4 Color;       // 頂点カラー.
};

///////////////////////////////////////////////////////////////////////////////
// VertexOutput structure
///////////////////////////////////////////////////////////////////////////////
struct VertexOutput
{
    float4 Position : SV_POSITION;
    float4 Color    : COLOR0;
};

///////////////////////////////////////////////////////////////////////////////
// PrimitiveOutput structure
///////////////////////////////////////////////////////////////////////////////
struct PrimitiveOutput
{
    uint PrimitiveId : INDEX0;
};

//-----------------------------------------------------------------------------
// Resources and Samplers.
//-----------------------------------------------------------------------------
StructuredBuffer<VertexInput>   Vertices    : register(t0);
StructuredBuffer<uint3>         Indices     : register(t1);

//-----------------------------------------------------------------------------
//      メインエントリーポイントです.
//-----------------------------------------------------------------------------
[numthreads(32, 1, 1)]
[outputtopology("triangle")]    // 新しいアトリビュート "line" または "triangle" が指定できる.
void main
(
    uint groupIndex : SV_GroupIndex,
    out vertices VertexOutput verts[3],         // verticesは必須属性.
    out indices uint3 tris[1],                  // indicesも必須属性.
    out primitives PrimitiveOutput prims[1]     // primitivesはオプション属性.
)
{
    // スレッドグループの頂点とプリミティブの数を設定.
    SetMeshOutputCounts(3, 1);

    if (groupIndex < 1)
    {
        tris[groupIndex] = Indices[groupIndex];         // 頂点インデックスを設定.
        prims[groupIndex].PrimitiveId = groupIndex;     // プリミティブインデックスを設定.
    }

    if (groupIndex < 3)
    {
        VertexOutput vout;
        vout.Position   = float4(Vertices[groupIndex].Position, 1.0f);
        vout.Color      = Vertices[groupIndex].Color;

        verts[groupIndex] = vout;   // 頂点を出力.
    }
}

入力アセンブラを使わないので,入力データのセマンティクスが不要になりダイレクトにStructuredBufferなどにアクセスしてデータを取ります。
メッシュシェーダのスレッドグループの最小値が32なので,[numthreads(32, 1, 1)]として設定しています。今回は3個の頂点を処理するので,29の非アクティブなスレッドが発生していますが,学習が目的なので今回は気にせずに無駄は許容ということにします。ちなみに,出力できるプリミティブ数の最大値は256,出力できる頂点の最大数が256,メッシュシェーダの最大スレッド数が128と上限があるので注意してください。このあたりの仕様についてはDirectX Specsに詳しく記載されているので,そちらを見てください。
メッシュシェーダは,頂点出力と頂点インデックス出力が必須となります。これらは47行目あたりからの

    out vertices VertexOutput verts[3],         // verticesは必須属性.
    out indices uint3 tris[1],                  // indicesも必須属性.
    out primitives PrimitiveOutput prims[1]     // primitivesはオプション属性.

で,指定を行っています。「out vertices データ型 仮引数名」「out indices データ型 仮引数名」といった感じにしてください。indicesのデータ型はライン描画する際はuint2に,トライアングル描画する際はuint3に指定します。ライン描画かトライアングル描画するかはoutputtoplogy属性で指定します。上のプログラムだと43行あたりで指定しています。

[outputtopology("triangle")]    // 新しいアトリビュート "line" または "triangle" が指定できる.

次に,メッシュシェーダ内の処理についてみていきます。シェーダの先頭で,スレッドグループから実際に出力される数を関数で設定しておく必要があります。これは次の関数で行います。  

void SetMeshOutputCounts(uint numVertices, uint numPrimitives);

この関数は1シェーダにつき,1回のみコール可能です。第1引数が出力する頂点数で,第2引数が出力するプリミティブ数になります。今回は1個のポリゴンを書きたいので,

    // スレッドグループの頂点とプリミティブの数を設定.
    SetMeshOutputCounts(3, 1);

と、指定しています。ちなみにSetMeshOutputCounts()の呼び出しを行わなかった場合は,シェーダの先頭でスレッドグループからエクスポートされる頂点とプリミティブの数が0に設定されるというメッシュシェーダの仕様により,メッシュが出力されない挙動となります。
あとの処理は,実際に出力するデータを組み立てていけばいいです。今回はスレッド数を32に設定しているので,SV_GroupIndexに0~31が来ます。これを頂点番号として使用してデータを組み立ています。シェーダでは早期リターンすると正しく動作しないことが多いので,コードは汚くなりますがif文で囲って処理を書くように注意してください。
続いてピクセルシェーダですが,いつも通りに書きます。下記のような感じです。

///////////////////////////////////////////////////////////////////////////////
// VertexOutput structure
///////////////////////////////////////////////////////////////////////////////
struct VertexOutput
{
    float4 Position : SV_POSITION;
    float4 Color    : COLOR0;
};

//-----------------------------------------------------------------------------
//      メインエントリーポイントです.
//-----------------------------------------------------------------------------
float4 main(VertexOutput input) : SV_TARGET
{
    return input.Color;
}

まぁ、特に説明することも無いですね。
さて、シェーダが用意できたのでCPU側の処理を書きます。メッシュシェーダにすることで変わるところはパイプラインステートの作成とIASetVertexBuffer()を使わない,IASetIndexBuffer()を使わない,DispatchMesh()を呼び出すといったところになります。まずはパイプラインステートの生成からです。メッシュシェーダ用いたパイプラインではD3D12_PIPELINE_STATE_STREAM_DESC構造体を使ってパイプラインステートを生成します。実装コードは次のような感じです。

    // パイプラインステートの生成.
    {
        // ラスタライザーステートの設定.
        D3D12_RASTERIZER_DESC descRS;
        descRS.FillMode                 = D3D12_FILL_MODE_SOLID;
        descRS.CullMode                 = D3D12_CULL_MODE_NONE;
        descRS.FrontCounterClockwise    = FALSE;
        descRS.DepthBias                = D3D12_DEFAULT_DEPTH_BIAS;
        descRS.DepthBiasClamp           = D3D12_DEFAULT_DEPTH_BIAS_CLAMP;
        descRS.SlopeScaledDepthBias     = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS;
        descRS.DepthClipEnable          = TRUE;
        descRS.MultisampleEnable        = FALSE;
        descRS.AntialiasedLineEnable    = FALSE;
        descRS.ForcedSampleCount        = 0;
        descRS.ConservativeRaster       = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF;

        // レンダーターゲットのブレンド設定.
        D3D12_RENDER_TARGET_BLEND_DESC descRTBS = {
            FALSE, FALSE,
            D3D12_BLEND_ONE, D3D12_BLEND_ZERO, D3D12_BLEND_OP_ADD,
            D3D12_BLEND_ONE, D3D12_BLEND_ZERO, D3D12_BLEND_OP_ADD,
            D3D12_LOGIC_OP_NOOP,
            D3D12_COLOR_WRITE_ENABLE_ALL
        };

        // ブレンドステートの設定.
        D3D12_BLEND_DESC descBS;
        descBS.AlphaToCoverageEnable  = FALSE;
        descBS.IndependentBlendEnable = FALSE;
        for( UINT i=0; i<D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i )
        { descBS.RenderTarget[i] = descRTBS; }

        D3D12_SHADER_BYTECODE ms;
        ms.pShaderBytecode  = SimpleMS;
        ms.BytecodeLength   = sizeof(SimpleMS);

        D3D12_SHADER_BYTECODE ps;
        ps.pShaderBytecode  = SimplePS;
        ps.BytecodeLength   = sizeof(SimplePS);

        D3D12_DEPTH_STENCILOP_DESC descStencil = {};
        descStencil.StencilFailOp       = D3D12_STENCIL_OP_KEEP;
        descStencil.StencilDepthFailOp  = D3D12_STENCIL_OP_KEEP;
        descStencil.StencilPassOp       = D3D12_STENCIL_OP_KEEP;
        descStencil.StencilFunc         = D3D12_COMPARISON_FUNC_ALWAYS;

        D3D12_DEPTH_STENCIL_DESC descDSS = {};
        descDSS.DepthEnable        = FALSE;
        descDSS.DepthWriteMask     = D3D12_DEPTH_WRITE_MASK_ALL;
        descDSS.DepthFunc          = D3D12_COMPARISON_FUNC_LESS_EQUAL;
        descDSS.StencilEnable      = FALSE;
        descDSS.StencilReadMask    = D3D12_DEFAULT_STENCIL_READ_MASK;
        descDSS.StencilWriteMask   = D3D12_DEFAULT_STENCIL_WRITE_MASK;
        descDSS.FrontFace          = descStencil;
        descDSS.BackFace           = descStencil;

        D3D12_RT_FORMAT_ARRAY rtvFormats = {};
        rtvFormats.NumRenderTargets = 1;
        rtvFormats.RTFormats[0]     = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;

        DXGI_SAMPLE_DESC descSample = {};
        descSample.Count    = 1;
        descSample.Quality  = 0;

        GEOMETRY_PIPELINE_STATE_DESC descGPS;
        descGPS.RootSignature           = m_pRootSignature.GetPtr();
        descGPS.MS                      = ms;
        descGPS.PS                      = ps;
        descGPS.BlendState              = descBS;
        descGPS.RasterizerState         = descRS;
        descGPS.DepthStencilState       = descDSS;
        descGPS.SampleMask              = UINT_MAX;
        descGPS.RTVFormats              = rtvFormats;
        descGPS.DSVFormat               = DXGI_FORMAT_D32_FLOAT;
        descGPS.SampleDesc              = descSample;
        descGPS.NodeMask                = 0;
        descGPS.Flags                   = D3D12_PIPELINE_STATE_FLAG_NONE;

        D3D12_PIPELINE_STATE_STREAM_DESC streamDesc = {};
        streamDesc.pPipelineStateSubobjectStream = &descGPS;
        streamDesc.SizeInBytes                   = sizeof(descGPS);

        hr = m_pDevice->CreatePipelineState(&streamDesc, IID_PPV_ARGS(m_pPipelineState.GetAddress()));
        if ( FAILED( hr ) )
        {
            ELOG( "Error : ID3D12Device::CreateGraphicsPipelineState() Failed." );
            return false;
        }
    }

上コードの,804行目付近で出てくる構造体は次のように定義しています。

///////////////////////////////////////////////////////////////////////////////
// PSSubObject class
///////////////////////////////////////////////////////////////////////////////
#pragma warning(push)
#pragma warning(disable : 4324)
template<typename InnerStructType, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE Type, typename DefaultArg = InnerStructType>
class alignas(void*) PSSubObject
{
public:
    PSSubObject() noexcept
    : m_Type    (Type)
    , m_Inner   (DefaultArg())
    { /* DO_NOTHING */ }

    PSSubObject(InnerStructType const& value) noexcept
    : m_Type    (Type)
    , m_Inner   (value)
    { /* DO_NOTHING */ }

    PSSubObject& operator = (InnerStructType const& value) noexcept
    {
        m_Type  = Type;
        m_Inner = value;
        return *this;
    }

    operator InnerStructType const&() const noexcept 
    { return m_Inner; }

    operator InnerStructType&() noexcept 
    { return m_Inner; }

    InnerStructType* operator&() noexcept
    { return &m_Inner; }

    InnerStructType const* operator&() const noexcept
    { return &m_Inner; }

private:
    D3D12_PIPELINE_STATE_SUBOBJECT_TYPE m_Type;
    InnerStructType                     m_Inner;
};
#pragma warning(pop)

using PSS_ROOT_SIGNATURE        = PSSubObject< ID3D12RootSignature*,            D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_ROOT_SIGNATURE >;
using PSS_AS                    = PSSubObject< D3D12_SHADER_BYTECODE,           D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_AS >;
using PSS_MS                    = PSSubObject< D3D12_SHADER_BYTECODE,           D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_MS >;
using PSS_PS                    = PSSubObject< D3D12_SHADER_BYTECODE,           D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_PS >;
using PSS_BLEND                 = PSSubObject< D3D12_BLEND_DESC,                D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_BLEND >;
using PSS_SAMPLE_MASK           = PSSubObject< UINT,                            D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_SAMPLE_MASK >;
using PSS_RASTERIZER            = PSSubObject< D3D12_RASTERIZER_DESC,           D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_RASTERIZER >;
using PSS_DEPTH_STENCIL         = PSSubObject< D3D12_DEPTH_STENCIL_DESC,        D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_DEPTH_STENCIL >;
using PSS_RTV_FORMATS           = PSSubObject< D3D12_RT_FORMAT_ARRAY,           D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_RENDER_TARGET_FORMATS >;
using PSS_DSV_FORMAT            = PSSubObject< DXGI_FORMAT,                     D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_DEPTH_STENCIL_FORMAT >;
using PSS_SAMPLE_DESC           = PSSubObject< DXGI_SAMPLE_DESC,                D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_SAMPLE_DESC >;
using PSS_NODE_MASK             = PSSubObject< UINT,                            D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_NODE_MASK >;
using PSS_CACHED_PSO            = PSSubObject< D3D12_CACHED_PIPELINE_STATE,     D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_CACHED_PSO >;
using PSS_FLAGS                 = PSSubObject< D3D12_PIPELINE_STATE_FLAGS,      D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_FLAGS >;


///////////////////////////////////////////////////////////////////////////////
// GEOMETRY_PIPELINE_STATE_DESC structure
///////////////////////////////////////////////////////////////////////////////
struct GEOMETRY_PIPELINE_STATE_DESC
{
    PSS_ROOT_SIGNATURE      RootSignature;
    PSS_AS                  AS;
    PSS_MS                  MS;
    PSS_PS                  PS;
    PSS_BLEND               BlendState;
    PSS_SAMPLE_MASK         SampleMask;
    PSS_RASTERIZER          RasterizerState;
    PSS_DEPTH_STENCIL       DepthStencilState;
    PSS_RTV_FORMATS         RTVFormats;
    PSS_DSV_FORMAT          DSVFormat;
    PSS_SAMPLE_DESC         SampleDesc;
    PSS_NODE_MASK           NodeMask;
    PSS_FLAGS               Flags;
};

入力レイアウトの指定もいらなくなっていることに注意してください。
GEOMETRY_PIPELINE_STATE_DESCのMSとPSに設定するシェーダは,次のインクルード文で変数が宣言されます。これは前述した「-Fh %(ProjectDir)..%(Filename).inc -Vn %(Filename)」を指定してdxcに渡すことで生成されるファイルになっています。

// Shaders
#include "../res/shaders/Compiled/SimpleMS.inc"
#include "../res/shaders/Compiled/SimplePS.inc"

ルートシグニチャは下記の設定になっています。

    // ルートシグニチャを生成.
    {
        // ルートパラメータの設定.
        D3D12_ROOT_PARAMETER param[2];
        param[0].ParameterType              = D3D12_ROOT_PARAMETER_TYPE_SRV;
        param[0].ShaderVisibility           = D3D12_SHADER_VISIBILITY_MESH;
        param[0].Descriptor.ShaderRegister  = 0;
        param[0].Descriptor.RegisterSpace   = 0;

        param[1].ParameterType              = D3D12_ROOT_PARAMETER_TYPE_SRV;
        param[1].ShaderVisibility           = D3D12_SHADER_VISIBILITY_MESH;
        param[1].Descriptor.ShaderRegister  = 1;
        param[1].Descriptor.RegisterSpace   = 0;

        // ルートシグニチャの設定.
        D3D12_ROOT_SIGNATURE_DESC desc;
        desc.NumParameters     = 2;
        desc.pParameters       = param;
        desc.NumStaticSamplers = 0;
        desc.pStaticSamplers   = nullptr;
        desc.Flags = D3D12_ROOT_SIGNATURE_FLAG_DENY_VERTEX_SHADER_ROOT_ACCESS
                   | D3D12_ROOT_SIGNATURE_FLAG_DENY_AMPLIFICATION_SHADER_ROOT_ACCESS
                   | D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS
                   | D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS
                   | D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS
                   | D3D12_ROOT_SIGNATURE_FLAG_DENY_PIXEL_SHADER_ROOT_ACCESS;

        asdx::RefPtr<ID3DBlob> pSignature;
        asdx::RefPtr<ID3DBlob> pError;

        // シリアライズする.
        hr = D3D12SerializeRootSignature(
            &desc,
            D3D_ROOT_SIGNATURE_VERSION_1,
            pSignature.GetAddress(),
            pError.GetAddress() );
        if ( FAILED( hr ) )
        {
            ELOG( "Error : D3D12SerializeRootSignataure() Failed." );
            return false;
        }

        // ルートシグニチャを生成.
        hr = m_pDevice->CreateRootSignature(
            0,
            pSignature->GetBufferPointer(),
            pSignature->GetBufferSize(),
            IID_PPV_ARGS(m_pRootSignature.GetAddress()) );
        if ( FAILED( hr ) )
        {
            ELOG( "Error : ID3D12Device::CreateRootSignature() Failed." );
            return false;
        }
    }

メッシュシェーダ用にD3D12_SHADER_VISIBILITY_MESHが追加されているので,ShaderVisibilityにこれを指定してあげてください。また,入力レイアウトを使わないので,D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUTの指定は不要になります。これでパイプラインステートの作成までが出来るはずです。
 あとは描画に使用する頂点バッファとインデックスバッファを構造化バッファ(StructuredBuffer)として用意しましょう。

    // 頂点バッファの生成.
    {
        // 頂点データ.
        Vertex vertices[] = {
            { {  0.0f,  0.5f, 0.0f }, { 1.0f, 0.0f, 0.0f, 1.0f } },
            { {  0.5f, -0.5f, 0.0f }, { 0.0f, 1.0f, 0.0f, 1.0f } },
            { { -0.5f, -0.5f, 0.0f }, { 0.0f, 0.0f, 1.0f, 1.0f } }
        };

        // ヒーププロパティの設定.
        D3D12_HEAP_PROPERTIES prop;
        prop.Type                 = D3D12_HEAP_TYPE_UPLOAD;
        prop.CPUPageProperty      = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
        prop.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
        prop.CreationNodeMask     = 1;
        prop.VisibleNodeMask      = 1;

        // リソースの設定.
        D3D12_RESOURCE_DESC desc;
        desc.Dimension          = D3D12_RESOURCE_DIMENSION_BUFFER;
        desc.Alignment          = 0;
        desc.Width              = sizeof(vertices);
        desc.Height             = 1;
        desc.DepthOrArraySize   = 1;
        desc.MipLevels          = 1;
        desc.Format             = DXGI_FORMAT_UNKNOWN;
        desc.SampleDesc.Count   = 1;
        desc.SampleDesc.Quality = 0;
        desc.Layout             = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
        desc.Flags              = D3D12_RESOURCE_FLAG_NONE;

        // リソースを生成.
        hr = m_pDevice->CreateCommittedResource(
            &prop,
            D3D12_HEAP_FLAG_NONE,
            &desc,
            D3D12_RESOURCE_STATE_GENERIC_READ,
            nullptr,
            IID_PPV_ARGS(m_pVertexBuffer.GetAddress()) );
        if ( FAILED( hr ) )
        {
            ELOG( "Error : ID3D12Device::CreateCommittedResource() Failed." );
            return false;
        }

        // マップする.
        UINT8* pData;
        hr = m_pVertexBuffer->Map( 0, nullptr, reinterpret_cast<void**>( &pData ) );
        if ( FAILED( hr ) )
        {
            ELOG( "Error : ID3D12Resource::Map() Failed." );
            return false;
        }

        // 頂点データをコピー.
        memcpy( pData, vertices, sizeof(vertices) );

        // アンマップする.
        m_pVertexBuffer->Unmap( 0, nullptr );


        D3D12_SHADER_RESOURCE_VIEW_DESC viewDesc = {};
        viewDesc.Format = DXGI_FORMAT_UNKNOWN;
        viewDesc.ViewDimension = D3D12_SRV_DIMENSION_BUFFER;
        viewDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
        viewDesc.Buffer.FirstElement = 0;
        viewDesc.Buffer.NumElements = 3;
        viewDesc.Buffer.StructureByteStride = sizeof(Vertex);
        viewDesc.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_NONE;

        m_pDevice->CreateShaderResourceView(m_pVertexBuffer.GetPtr(), &viewDesc, handleCPU);

        m_VerticesSRV.HandleCPU = handleCPU;
        m_VerticesSRV.HandleGPU = handleGPU;
        handleCPU.ptr += m_ResDescriptorSize;
        handleGPU.ptr += m_ResDescriptorSize;
    }

    // インデックスバッファ.
    {
        uint32_t indices[] = { 0, 1, 2 };

        // ヒーププロパティの設定.
        D3D12_HEAP_PROPERTIES prop;
        prop.Type                 = D3D12_HEAP_TYPE_UPLOAD;
        prop.CPUPageProperty      = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
        prop.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
        prop.CreationNodeMask     = 1;
        prop.VisibleNodeMask      = 1;

        // リソースの設定.
        D3D12_RESOURCE_DESC desc;
        desc.Dimension          = D3D12_RESOURCE_DIMENSION_BUFFER;
        desc.Alignment          = 0;
        desc.Width              = sizeof(indices);
        desc.Height             = 1;
        desc.DepthOrArraySize   = 1;
        desc.MipLevels          = 1;
        desc.Format             = DXGI_FORMAT_UNKNOWN;
        desc.SampleDesc.Count   = 1;
        desc.SampleDesc.Quality = 0;
        desc.Layout             = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
        desc.Flags              = D3D12_RESOURCE_FLAG_NONE;

        // リソースを生成.
        hr = m_pDevice->CreateCommittedResource(
            &prop,
            D3D12_HEAP_FLAG_NONE,
            &desc,
            D3D12_RESOURCE_STATE_GENERIC_READ,
            nullptr,
            IID_PPV_ARGS(m_pIndexBuffer.GetAddress()) );
        if ( FAILED( hr ) )
        {
            ELOG( "Error : ID3D12Device::CreateCommittedResource() Failed." );
            return false;
        }

        // マップする.
        uint8_t* pData;
        hr = m_pIndexBuffer->Map( 0, nullptr, reinterpret_cast<void**>( &pData ) );
        if ( FAILED( hr ) )
        {
            ELOG( "Error : ID3D12Resource::Map() Failed." );
            return false;
        }

        // 頂点データをコピー.
        memcpy( pData, indices, sizeof(indices) );

        // アンマップする.
        m_pIndexBuffer->Unmap( 0, nullptr );

        D3D12_SHADER_RESOURCE_VIEW_DESC viewDesc = {};
        viewDesc.Format                     = DXGI_FORMAT_UNKNOWN;
        viewDesc.ViewDimension              = D3D12_SRV_DIMENSION_BUFFER;
        viewDesc.Shader4ComponentMapping    = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
        viewDesc.Buffer.FirstElement        = 0;
        viewDesc.Buffer.NumElements         = 1;
        viewDesc.Buffer.StructureByteStride = sizeof(uint32_t) * 3;
        viewDesc.Buffer.Flags               = D3D12_BUFFER_SRV_FLAG_NONE;

        m_pDevice->CreateShaderResourceView(m_pIndexBuffer.GetPtr(), &viewDesc, handleCPU);

        m_IndicesSRV.HandleCPU = handleCPU;
        m_IndicesSRV.HandleGPU = handleGPU;
        handleCPU.ptr += m_ResDescriptorSize;
        handleGPU.ptr += m_ResDescriptorSize;
    }

これも普通に作ればいいだけなので,特に説明もいらないでしょう。
 ここまでで,データの作成が出来ました。あとはコマンドリストに描画コマンドを積みましょう。

//-------------------------------------------------------------------------------------------------
//      描画時の処理です.
//-------------------------------------------------------------------------------------------------
void App::OnRender(FLOAT elapsedSec)
{
    // コマンドアロケータとコマンドリストをリセット.
    m_pCmdAllocator->Reset();
    m_pCmdList->Reset( m_pCmdAllocator.GetPtr(), m_pPipelineState.GetPtr() );

    // ディスクリプタヒープを設定.
    m_pCmdList->SetDescriptorHeaps( 1, m_pResHeap.GetAddress() );

    // ルートシグニチャを設定.
    m_pCmdList->SetGraphicsRootSignature( m_pRootSignature.GetPtr() );

    // ディスクリプタヒープテーブルを設定.
    m_pCmdList->SetGraphicsRootShaderResourceView(0, m_pVertexBuffer->GetGPUVirtualAddress());
    m_pCmdList->SetGraphicsRootShaderResourceView(1, m_pIndexBuffer->GetGPUVirtualAddress());

    // ビューポートの設定.
    m_pCmdList->RSSetViewports( 1, &m_Viewport );

    // シザー矩形の設定.
    m_pCmdList->RSSetScissorRects( 1, &m_ScissorRect );

    // リソースバリアの設定.
    // Present ---> RenderTarget
    D3D12_RESOURCE_BARRIER barrier;
    ZeroMemory( &barrier, sizeof(barrier) );
    barrier.Type                    = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
    barrier.Flags                   = D3D12_RESOURCE_BARRIER_FLAG_NONE;
    barrier.Transition.pResource    = m_pRenderTarget[m_FrameIndex].GetPtr();
    barrier.Transition.StateBefore  = D3D12_RESOURCE_STATE_PRESENT;
    barrier.Transition.StateAfter   = D3D12_RESOURCE_STATE_RENDER_TARGET;
    barrier.Transition.Subresource  = D3D12_RESOURCE_BARRIER_FLAG_NONE;
    m_pCmdList->ResourceBarrier( 1, &barrier );

    // レンダーターゲットのハンドルを取得.
    auto handleRTV = m_pRtvHeap->GetCPUDescriptorHandleForHeapStart();
    auto handleDSV = m_pDsvHeap->GetCPUDescriptorHandleForHeapStart();
    handleRTV.ptr += ( m_FrameIndex * m_RtvDescriptorSize );

    // レンダーターゲットの設定.
    m_pCmdList->OMSetRenderTargets( 1, &handleRTV, FALSE, &handleDSV );

    // レンダーターゲットビューをクリア.
    const FLOAT clearColor[] = { 0.39f, 0.58f, 0.92f, 1.0f };
    m_pCmdList->ClearRenderTargetView( handleRTV, clearColor, 0, nullptr );

    // 深度ステンシルビューをクリア.
    m_pCmdList->ClearDepthStencilView( handleDSV, D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr );

    // メッシュ描画.
    m_pCmdList->DispatchMesh(1, 1, 1);

    // リソースバリアの設定.
    // RenderTarget ---> Present
    barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
    barrier.Transition.StateAfter  = D3D12_RESOURCE_STATE_PRESENT;
    m_pCmdList->ResourceBarrier( 1, &barrier );

    // コマンドの記録を終了.
    m_pCmdList->Close();

    // コマンド実行.
    ID3D12CommandList* ppCmdLists[] = { m_pCmdList.GetPtr() };
    m_pCmdQueue->ExecuteCommandLists( _countof(ppCmdLists), ppCmdLists );

    // 表示する.
    m_pSwapChain->Present( 1, 0 );

    // コマンドの完了を待機.
    WaitForGpu();
}

頂点バッファとインデックスバッファはSRVとして設定します。そのためIASetIndexBuffer(), IASetVertexBuffers()は使いません。DrawIndex()の代わりにコンピュートシェーダのDispatch()のようにDispatchMesh()をコールします。

    // メッシュ描画.
    m_pCmdList->DispatchMesh(1, 1, 1);

これで終わりです。
問題なければ,画面にポリゴンが表示されるはずです。

描画結果
図 2: 描画結果

今回は,メッシュシェーダを使ったポリゴン描画について簡単な紹介をしました。
本格的な使い方は,時間が出来たら書くことにします。しばしお待ちを!

5 参考文献

6 サンプルコード

本ソースコードおよびプログラムはMIT Licenseに準じます。
プログラムの作成にはMicrosoft Visual Studio Community 2019, 及び Windows SDK 10.0.20161.0を用いています。
Github - D3D12_SimpleMeshShader