DirectX11, comment gérer et mettre à jour plusieurs tampons constants de shader?

13

D'accord, j'ai du mal à comprendre comment les tampons constants sont liés à une étape de pipeline et mis à jour. Je comprends que DirectX11 peut avoir jusqu'à 15 tampons constants de shader par étape et chaque tampon peut contenir jusqu'à 4096 constantes. Cependant, je ne comprends pas si le COM ID3D11Buffer utilisé pour interagir avec les tampons constants n'est qu'un mécanisme (ou poignée) utilisé pour remplir ces emplacements de tampon ou si l'objet fait réellement référence à une instance particulière de données de tampon qui est poussée d'avant en arrière entre le GPU et le CPU.

Je pense que ma confusion sur le sujet est la cause d'un problème que j'utilise avec deux tampons constants différents.

Voici un exemple de code de shader.

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

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

La façon dont mon code est organisé, la caméra gérera la mise à jour des données pertinentes par image et GameObjects mettra à jour leurs propres données par objet. Les deux classes ont leur propre ID3D11Buffer qui est utilisé pour ce faire (en utilisant une architecture concentrateur, donc une classe GameObject gérera le rendu de tous les GameObjects instanciés dans le monde).

Le problème est que je ne peux obtenir qu'une mise à jour à la fois, en fonction de l'emplacement et je suppose que l'ordre de mise à jour un tampon est rempli tandis que l'autre est mis à zéro.

C'est essentiellement mon code. Les deux classes utilisent une logique de mise à jour identique.

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);

Mes principales questions sont -

  • Dois-je définir ou lier le ShaderBuffer afin de le mettre à jour avec l'appel UpdateSubresource? (Cela signifie que vous ne devez le manipuler que lorsqu'il est dans le pipeline). Ou s'agit-il d'un blob de données qui sera envoyé avec l'appel VSSetConstantBuffer? (Cela signifie que l'ordre de liaison et de mise à jour des données n'a pas d'importance, je peux le mettre à jour dans le pipeline ou d'une manière ou d'une autre sur le processeur)
  • Lors de la définition ou de la liaison du tampon, dois-je faire référence à l'emplacement 0 pour mettre à jour le tampon PerFrame et à l'emplacement 1 pour mettre à jour le tampon PerObject? Une sorte de confusion avec cet appel dans mon code pourrait-elle entraîner l'écrasement de tous les tampons?
  • Comment le D3D11 sait-il quel tampon je souhaite mettre à jour ou mapper? Sait-il du COM ID3D11Buffer utilisé?

Éditer -

Modification des balises de registre de tampon constant dans l'exemple ci-dessus. L'utilisation de (cb #) au lieu de (b #) empêchait les tampons de se mettre à jour correctement pour une raison quelconque. Je ne sais pas où j'ai récupéré la syntaxe d'origine ou si elle est valide, mais cela semble avoir été mon principal problème.

KlashnikovKid
la source

Réponses:

18

L'ID3D11Buffer fait référence à un bloc de mémoire réel qui contient vos données, qu'il s'agisse d'un tampon de sommet, d'un tampon constant ou autre.

Les tampons constants fonctionnent de la même manière que les tampons de sommet et d'autres types de tampons. À savoir, les données qu'elles contiennent ne sont pas accessibles par le GPU jusqu'à ce qu'elles rendent réellement la trame, donc le tampon doit rester valide jusqu'à ce que le GPU en ait fini avec. Vous devez double-tamponner chaque tampon constant, de sorte que vous avez une copie à mettre à jour pour l'image suivante et une copie à lire par le GPU lors du rendu de l'image actuelle. Ceci est similaire à la façon dont vous feriez des tampons de sommet dynamiques pour un système de particules ou similaire.

Les register(cb0), register(cb1)paramètres concordent avec ceux du HLSL avec les fentes VSSetConstantBuffers. Lorsque vous mettez à jour les constantes par image, vous devez VSSetConstantBuffers(0, 1, &pBuffer)définir CB0 et lorsque vous mettez à jour celles par objet, vous VSSetConstantBuffers(1, 1, &pBuffer)définissez CB1. Chaque appel ne met à jour que les tampons référencés par les paramètres start / count et ne touche pas les autres.

Vous n'avez pas besoin de lier le tampon pour le mettre à jour avec UpdateSubresource. En fait, il ne devrait pas être lié lorsque vous le mettez à jour, ou cela peut forcer le pilote à faire des copies de mémoire supplémentaire en interne (voir la page MSDN pour UpdateSubresource, notamment les remarques sur les conflits concernant une page vers le bas).

Je ne sais pas ce que vous entendez par "Comment le D3D11 sait-il quel tampon je veux mettre à jour ou mapper?" Il met à jour ou mappe celui dont vous avez transmis le pointeur.

Nathan Reed
la source
3

Il semble y avoir beaucoup de confusion autour du sujet de la nécessité de relier les tampons constants après les avoir mis à jour. En apprenant cela moi-même, j'ai vu beaucoup de sujets et de discussions avec des opinions opposées à ce sujet. À savoir la meilleure réponse ici, recommandant d'appeler XXSetConstantBuffersaprès la mise à jour via UpdateSubresourceou Map/Unmap.

En outre, certains exemples et documentation D3D MSDN semblent utiliser ce modèle, liant (appelant XXSetConstantBuffers) par image ou même par objet dessiné, même s'ils ne mettent à jour qu'un tampon existant et ne modifient pas un emplacement spécifique avec un tampon complètement différent .

Je pense que la pire idée fausse est qu'en XXSetConstantBuffersfait "envoie les données que vous avez précédemment mises à jour au GPU ou l'avertit de la mise à jour, de sorte qu'il prenne les nouvelles valeurs - ce qui semble être complètement faux.

En effet, lors de l'utilisation de UpdateSubresourceou Map/Unmap, la documentation indique que plusieurs copies internes peuvent être effectuées par le GPU s'il a encore besoin des anciennes données, mais cela ne préoccupe pas l'utilisateur de l'API lorsqu'il s'agit de mettre à jour un tampon déjà lié. Par conséquent, la nécessité de se défaire explicitement semble superflue.

Au cours de mon expérimentation, je suis arrivé à la conclusion qu'il n'est pas nécessaire de lier à nouveau les tampons via XXSetConstantBuffersaprès leur mise à jour, à moins qu'ils ne soient pas déjà liés! Tant que vous utilisez les mêmes tampons (partagés entre les shaders, les étapes de pipeline eveny) qui sont une fois liés (lors de la phase de démarrage, par exemple), vous n'avez pas besoin de les lier à nouveau - il suffit de les mettre à jour.

Du code pour mieux mettre en valeur la nature de mes expériences:

// 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);
    }
}

Voici quelques sujets sur Internet (forums gamedev) qui semblent adopter et recommander cette approche: http://www.gamedev.net/topic/649410-set-constant-buffers-every-frame/?view=findpost&p=5105032 et http://www.gamedev.net/topic/647203-updating-constant-buffers/#entry5090000

Pour conclure, il semble vraiment que la liaison n'est pas nécessaire à moins que vous ne changiez complètement le tampon, mais tant que vous partagez les tampons et leur disposition entre les shaders (pratique recommandée), la liaison doit être effectuée dans les cas suivants:

  • Au démarrage - liaison initiale - après la création du tampon par exemple.
  • Si vous avez besoin / avez conçu d'utiliser plus d'un tampon lié à un emplacement spécifique d'une ou plusieurs étapes.
  • Après avoir effacé l'état de deviceContext (lors du redimensionnement des tampons / fenêtres)
Rocky Raccoon
la source