DirectX

DirectX(D3D11.1)再入門 ConstantBuffer

More than 3 years have passed since last update.

今回のコード
今回は、予定通りシェーダーに変数を導入します。

シェーダーにグローバル変数を導入して、
VertexShaderで三角形の頂点位置に適用して、三角形を回転いたします。

シェーダー側

の増えたところ

コンスタント変数の定義(hlsl)
// 定義
cbuffer c0
{
    // 変数
    float4x4 ModelMatrix;
};

VS_OUTPUT vsMain( VS_INPUT In )
{
    VS_OUTPUT Output;
    // 使う
    Output.Position = mul(In.Position, ModelMatrix);
    Output.Color    = In.Color;
    return Output;    
}

プログラム側

シェーダーで宣言した定数をプログラム側から変更するにはコンスタントバッファを経由する必要がある。
コンスタントバッファには主に生成、更新、シェーダーステージへのセットという3つのアクションがある。
生成はデバイスのメソッドで、更新とセットはデバイスコンテキストの仕事だ。

コンスタントバッファを生成(c++)
        D3D11_BUFFER_DESC desc = { 0 };

        // 注意!最低でも16にしないと失敗になるっぽい
        // むしろ16の倍数でないといけないとかあるかも(未調査)。
        desc.ByteWidth = sizeof(DirectX::XMMATRIX);
        desc.Usage = D3D11_USAGE_DEFAULT;
        desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;

        HRESULT hr = pDevice->CreateBuffer(&desc, nullptr, &m_pBuffer);
        if (FAILED(hr)){
            return false;
        }
コンスタントバッファを更新(c++)
pDeviceContext->UpdateSubresource(m_pBuffer, 0, NULL, &Buffer, 0, 0);
コンスタントバッファをシェーダーステージにセットする(c++)
// VSの場合
pDeviceContext->VSSetConstantBuffers(0, 1, &m_pBuffer);

行列型の扱い(DirectXMath)

シェーダー変数
    float4x4 ModelMatrix;

にプログラム側で対応するのは、

float m[16];

だけど最近のDirectXには今は亡きD3DXに代わってDirectXMathがあるので

#include <DirectXMath.h>

DirectX::XMFLOAT4X4 matrix;

を使おう。

using namespace DirectX;

とかするとXNAMathと似た感じなる(XNAMathは使ったことないのだが・・・)。

DirectXMathメモ

DirectXMathは慣れないと大変使いにくいの軽く説明を。
DirectXMathでは演算に使う型と入れ物に使う型を区別して使う必要がある。
例えば、4x4行列の演算型はDirectX::XMMATRIXで、入れ物はDirectX::XMFLOAT4X4
となる。型名の後半がFLOATとメンバー数である方が入れ物用でメンバ関数とかコンストラクタの類の無いいわゆるPODになっているようだ。
問題は演算用の型の扱いで知らないと詰む。
ポイントは下記の点。

  • SIMD向けに最適化されており直接値を代入したりメンバ関数にアクセスしたりできない
  • SIMD向けなのでアライメントとかがある

間違った使い方(入れ物としてメンバ変数にするとか)をすると大概コンパイルエラーになるのだが、
アライメントの設定とかをしてこれを乗り越えたとしても、VCのバージョンとかによって不可解な挙動なったりする感じで迂闊に扱うと危険なのである。
こちらにヒープに確保しない方がよいのではないかと書かれていた。
今回のプログラムでその辺りのことをやっているのが下記のコード。
XMMATRIXはローカル変数で取り回すようにした。

DirectXMathで回転行列を作る
        static float angleRadians = 0;
        const auto DELTA = DirectX::XMConvertToRadians(0.1f);
        angleRadians += DELTA;
        // XMMATRIX型の値を作る!
        //auto m = DirectX::XMMatrixIdentity();
        auto m = DirectX::XMMatrixRotationZ(angleRadians);

        // XMMATRIX型からXMFloat4x4型にStoreする
        DirectX::XMStoreFloat4x4(&m_constant->Buffer.Model, m);

ss.png
二次元的な意味で回る三角形。

定数バッファ詳細

以上で変数が一つの時の例は動きました。
ただ、まだ詳細が不明な部分が多いのでもう少し突っ込んでまいりたいと思います。

リフレクション

とりあえずリフレクションでシェーダーから定数バッファ情報を取得してみた。

            Microsoft::WRL::ComPtr<ID3DBlob> vblob;
            HRESULT hr = CompileShaderFromFile(shaderFile.c_str(), vsFunc.c_str(), "vs_4_0_level_9_1", &vblob);
            if (FAILED(hr))
                return false;
            hr = pDevice->CreateVertexShader(vblob->GetBufferPointer(), vblob->GetBufferSize(), NULL, &m_pVsh);
            if (FAILED(hr))
                return false;

            // vertex shader reflection
            Microsoft::WRL::ComPtr<ID3D11ShaderReflection> pReflector;
            hr = D3DReflect(vblob->GetBufferPointer(), vblob->GetBufferSize(), IID_ID3D11ShaderReflection, &pReflector);
            if (FAILED(hr))
                return false;

            OutputDebugPrintfA("#### VertexShader ####\n");
            parseConstantBuffer(pReflector);

// 省略

    void parseConstantBuffer(const Microsoft::WRL::ComPtr<ID3D11ShaderReflection> &pReflector)
    {
        D3D11_SHADER_DESC shaderdesc;
        pReflector->GetDesc(&shaderdesc);

        // analize constant buffer
        for (size_t i = 0; i < shaderdesc.ConstantBuffers; ++i){
            auto cb = pReflector->GetConstantBufferByIndex(i);
            D3D11_SHADER_BUFFER_DESC desc;
            cb->GetDesc(&desc);
            OutputDebugPrintfA("[%d: %s]\n", i, desc.Name);

            for (size_t j = 0; j < desc.Variables; ++j){
                auto v = cb->GetVariableByIndex(j);
                D3D11_SHADER_VARIABLE_DESC vdesc;
                v->GetDesc(&vdesc);
                OutputDebugPrintfA("(%d) %s %d\n", j, vdesc.Name, vdesc.StartOffset);
            }
        }
    }

VertexShaderとPixelShaderで両方やってみたところ下記のように、VertexShaderStageにしか定数バッファが存在しなかった。ステージごとに有効な変数しか定数バッファに入らないことが判明。今回作ったConstantBufferは筋が悪い。いちいち使い手が手数バッファのレイアウトを気にしたコードを書くのはよろしくないので、リフレクションを駆使してキーバリューストア的なインタフェースを実装せねばなるまい。

#### VertexShader ####
[0: c0]
(0) ModelMatrix 0
#### PixelShader ####

cbufferの省略

cbufferの記述を省略して単なるグローバル変数にしてみた。

コンスタント変数の定義(hlsl)
// 定義
//cbuffer c0
//{
    // 変数
    float4x4 ModelMatrix;
//};

リフレクションをしてみると下記のようなログが取れた。
cbufferに収納されない変数は、"$Globals"という名のコンスタントバッファが自動的に割り当てられるらしい。

#### VertexShader ####
[0: $Globals]
(0) ModelMatrix 0
#### PixelShader ####

次回、角括弧でアクセスできる定数バッファを作って三角錐が三次元的な意味で回転する。予定。

参考

ConstantBuffer

DirectXMath