DirectX11、複数のシェーダー定数バッファーを管理および更新するにはどうすればよいですか?


13

さて、定数バッファがパイプラインステージにバインドされて更新される方法を把握するのは大変です。DirectX11では、ステージごとに最大15個のシェーダー定数バッファーを使用でき、各バッファーには最大4096個の定数を保持できることを理解しています。ただし、定数バッファーと対話するために使用されるID3D11Buffer COMが、これらのバッファースロットを埋めるために使用されるメカニズム(またはハンドル)だけであるか、またはオブジェクトが実際に前後にプッシュされるバッファーデータの特定のインスタンスを参照するかどうかがわかりませんGPUとCPUの間。

このトピックに関する混乱は、2つの異なる定数バッファーを使用している問題の原因だと思います。

シェーダーコードの例を次に示します。

cbuffer PerFrame : register(b0) {
    float4x4 view;
};

cbuffer PerObject : register(b1) {
    float4x4 scale;
    float4x4 rotation;
    float4x4 translation;
};

コードの編成方法では、カメラはフレームごとの関連データの更新を処理し、GameObjectsはオブジェクトごとの独自のデータを更新します。両方のクラスには、これを行うために使用される独自のID3D11Bufferがあります(ハブアーキテクチャを使用しているため、1つのGameObjectクラスが、世界中のインスタンス化されたGameObjectsのレンダリングを処理します)。

問題は、スロットに応じて一度に1つしか更新できないことです。更新順序は、1つのバッファーが満たされ、もう1つのバッファーがゼロになると仮定しています。

これは基本的に私のコードです。両方のクラスは同一の更新ロジックを使用します。

static PerObjectShaderBuffer _updatedBuffer; // PerFrameShaderBuffer if Camera class
_updatedBuffer.scale       = _rScale;
_updatedBuffer.rotation    = _rRotation;
_updatedBuffer.translation = _rTranslation;
pDeviceContext->UpdateSubresource(pShaderBuffer, 0 , 0, &_updatedBuffer, 0, 0);

pDeviceContext->VSSetShader(pVShader->GetShaderPtr(), 0, 0);
pDeviceContext->PSSetShader(pPShader->GetShaderPtr(), 0, 0);
pDeviceContext->VSSetConstantBuffers(1, 1, &pShaderBuffer);
pDeviceContext->IASetVertexBuffers(0, 1, &pVertexBuffer, &vStride, &_offset );
pDeviceContext->IASetPrimitiveTopology(topologyType);
pDeviceContext->Draw(bufSize, 0);

私の主な質問は-

  • UpdateSubresource呼び出しで更新するには、ShaderBufferを設定またはバインドする必要がありますか?(つまり、パイプラインにある場合にのみ操作します)またはVSSetConstantBuffer呼び出しで送信されるデータのblobですか?(データのバインドと更新の順序は重要ではありません。パイプラインまたはCPUで何らかの方法で更新できます)
  • バッファーを設定またはバインドするときに、スロット0を参照してPerFrameバッファーを更新し、スロット1を参照してPerObjectバッファーを更新する必要がありますか?私のコードでこの呼び出しを使用すると、すべてのバッファが上書きされる可能性がありますか?
  • D3D11は、どのバッファーを更新またはマップするのかをどのようにして知るのですか?使用されているID3D11Buffer COMから知っていますか?

編集-

上記の例の定数バッファレジスタタグを変更しました。(b#)の代わりに(cb#)を使用すると、何らかの理由でバッファが正しく更新されないことがありました。元の構文をどこで取得したか、それがまったく有効かどうかはわかりませんが、それが私の主な問題のようです。

回答:


18

ID3D11Bufferは、頂点バッファー、定数バッファーなど、データを保持する実際のメモリチャンクを参照します。

定数バッファーは、頂点バッファーや他の種類のバッファーと同じように機能します。つまり、それらのデータは、実際にフレームをレンダリングするまでGPUによってアクセスされないため、GPUが処理を完了するまでバッファーは有効なままでなければなりません。各定数バッファーをダブルバッファーする必要があります。そのため、次のフレームで更新するコピーが1つあり、現在のフレームのレンダリング中にGPUが読み取るコピーが1つあります。これは、パーティクルシステムなどに動的な頂点バッファを使用する方法と似ています。

register(cb0)register(cb1)VSSetConstantBuffersのスロットとHLSLの対応の設定。フレームごとの定数VSSetConstantBuffers(0, 1, &pBuffer)を更新するとCB0を設定し、オブジェクトごとの定数を更新するとVSSetConstantBuffers(1, 1, &pBuffer)CB1を設定します。各呼び出しは、start / countパラメーターによって参照されるバッファーのみを更新し、他のバッファーには影響しません。

UpdateSubresourceで更新するためにバッファをバインドする必要はありません。実際、更新時にバインドされるべきはありません。そうしないと、ドライバーが内部で追加のメモリコピーを強制的に作成する可能性があります(UpdateSubresource のMSDNページ、特にページダウンに関する競合の発言を参照)。

「D3D11は、どのバッファーを更新またはマップするのかをどのようにして知るのですか?」の意味がわかりません。ポインタを渡したものを更新またはマッピングします。


3

定数バッファを更新した後に再バインドする必要があるというトピックについては、多くの混乱があるようです。私はこれについて自分で学んでいるので、これに関して反対の意見を持つ多くのトピックや議論を見てきました。つまり、ここでの最良の答えは、またはXXSetConstantBuffersを介して更新した後に呼び出すことをお勧めします。UpdateSubresourceMap/Unmap

また、一部のD3D MSDNサンプルおよびドキュメントでは、既存のバッファーを更新するだけで、特定のスロットを完全に異なるバッファーで変更しない場合でも、フレームごとまたは描画オブジェクトごとにバインド(呼び出しするこのパターンを使用しているようですXXSetConstantBuffers

最悪の誤解は、XXSetConstantBuffers「以前に更新したデータを実際にGPUに送信するか、更新を通知するため、新しい値を取得するということです。これは完全に間違っているようです。

実際、UpdateSubresourceまたはを使用する場合Map/Unmap、ドキュメントは、古いデータが必要な場合、GPUによって内部的に複数のコピーが作成される可能性があると述べていますが、既にバインドされたバッファーの更新に関しては、APIのユーザーにとっては問題ではありません。したがって、明示的にバインドを解除する必要はありません。

実験中に、バッファーXXSetConstantBuffersがまだバインドされていない限り、更新後にバッファーを再バインドする必要はないという結論に達しました!一度バインドされた同じバッファー(シェーダー、均等なパイプラインステージ間で共有される)を使用する限り(たとえば、起動フェーズで)、それらを再バインドする必要はなく、更新するだけです。

私の実験の性質をよりよく示すいくつかのコード:

// Memory double of the buffer (static)
ConstBuffer* ShaderBase::CBuffer = (ConstBuffer*)_aligned_malloc(sizeof(ConstBuffer), 16);
// Hardware resource pointer (static)
ID3D11Buffer* ShaderBase::m_HwBuffer = nullptr;

void ShaderBase::Buffer_init()
{
     // Prepare buffer description etc.
     // Create one global buffer shared across shaders
     result = device->CreateBuffer(&cBufferDesc, NULL, &m_HwBuffer);
     BindConstBuffer();
}

...

void ShaderBase::BindConstBuffer()
{
     // Bind buffer to both VS and PS stages since it's a big global one
     deviceContext->VSSetConstantBuffers(0, 1, &m_HwBuffer);
     deviceContext->PSSetConstantBuffers(0, 1, &m_HwBuffer);
}

...
bool ShaderBase::UpdateConstBuffers()
{
    ...
    D3D11_MAPPED_SUBRESOURCE mappedResource;

    // Lock the constant buffer so it can be written to.
    deviceContext->Map(m_HwBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);

    // Get a pointer to the data in the constant buffer.
    ConstBuffer* dataPtr = (ConstBuffer*)mappedResource.pData;
    memcpy(dataPtr, CBuffer, sizeof(ConstBuffer));

    // Unlock the constant buffer.
    deviceContext->Unmap(m_HwBuffer, 0);
    return true;
}

// May be called multiple times per frame (multiple render passes)
void DrawObjects()
{
    // Simplified version
    for each Mesh _m to be drawn
    {
        // Some changes are per frame - but since we have only one global buffer to which we 
        // write with write-discard we need to set all of the values again when we update per-object
        ShaderBase::CBuffer->view = view;
        ShaderBase::CBuffer->projection = projection;
        ShaderBase::CBuffer->cameraPosition = m_Camera->GetPosition();

        ... 

        ShaderBase::CBuffer->lightDirection = m_Light->GetDirection();

        ShaderBase::CBuffer->lightView = lightView;
        ShaderBase::CBuffer->lightProjection = lightProjection;
        ShaderBase::CBuffer->world = worldTransform;

        // Only update! No rebind!
        if (ShaderBase::UpdateConstBuffers() == false)
            return false;

        _m->LoadIABuffers(); // Set the vertex and index buffers for the mesh
        deviceContext->DrawIndexed(_m->indexCount, 0, 0);
    }
}

ここでは、このアプローチを採用してお勧めしているように見えるいくつかのインターネットからの話題(gamedevフォーラム)は、次のとおりです。 http://www.gamedev.net/topic/649410-set-constant-buffers-every-frame/?view=findpost&p=5105032および http://www.gamedev.net/topic/647203-updating-constant-buffers/#entry5090000

結論として、バッファーを完全に変更しない限りバインディングは必要ないと思われますが、バッファーとシェーダー間でレイアウトを共有している限り(推奨される方法)、これらの場合にバインディングを実行する必要があります。

  • 起動時-初期バインディング-バッファの作成後など。
  • 1つ以上のステージの特定のスロットにバインドされた複数のバッファーを使用するように設計または設計している場合。
  • deviceContextの状態をクリアした後(バッファー/ウィンドウのサイズを変更する場合)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.