DirectX11, come posso gestire e aggiornare più buffer costanti di shader?


13

Va bene, sto facendo fatica a capire come i buffer costanti sono legati a uno stadio della pipeline e aggiornati. Comprendo che DirectX11 può avere fino a 15 buffer di shader costanti per fase e ogni buffer può contenere fino a 4096 costanti. Tuttavia, non capisco se la COM ID3D11Buffer utilizzata per interagire con i buffer costanti sia solo un meccanismo (o handle) utilizzato per riempire questi slot del buffer o se l'oggetto fa effettivamente riferimento a una particolare istanza di dati buffer che viene spinta avanti e indietro tra GPU e CPU.

Penso che la mia confusione sull'argomento sia la causa di un problema che sto avendo usando due diversi buffer costanti.

Ecco alcuni esempi di codice shader.

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

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

Il modo in cui è organizzato il mio codice, la telecamera gestirà l'aggiornamento dei dati rilevanti per frame e GameObjects aggiornerà i propri dati per oggetto. Entrambe le classi hanno il proprio ID3D11Buffer che viene utilizzato per fare ciò (utilizzando un'architettura hub, quindi una classe GameObject gestirà il rendering di tutti gli GameObject istanziati nel mondo).

Il problema è che posso aggiornarne solo uno alla volta, a seconda dello slot e presumo che l'ordine di aggiornamento venga riempito da un buffer mentre l'altro venga azzerato.

Questo è essenzialmente il mio codice. Entrambe le classi usano la stessa logica di aggiornamento.

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

Le mie domande principali sono:

  • Devo impostare o associare ShaderBuffer per aggiornarlo con la chiamata UpdateSubresource? (Il significato lo manipola solo quando è nella pipeline) O è un blocco di dati che verrà inviato con la chiamata VSSetConstantBuffer? (Significa che l'ordine di associazione e aggiornamento dei dati non ha importanza, posso aggiornarlo nella pipeline o in qualche modo sulla CPU)
  • Quando si imposta o si associa il buffer, è necessario fare riferimento allo slot 0 per aggiornare il buffer PerFrame e lo slot 1 per aggiornare il buffer PerObject? Una sorta di confusione con questa chiamata nel mio codice potrebbe causare la sovrascrittura di tutti i buffer?
  • Come fa D3D11 a sapere quale buffer voglio aggiornare o mappare? Lo sa dal COM ID3D11Buffer utilizzato?

Modificare -

Modificati i tag del registro buffer costante nell'esempio sopra. L'uso di (cb #) invece di (b #) ha influito negativamente sull'aggiornamento corretto dei buffer per qualche motivo. Non sono sicuro di dove abbia preso la sintassi originale o se sia valida, ma sembra essere stato il mio problema principale.

Risposte:


18

ID3D11Buffer fa riferimento a un blocco di memoria effettivo che contiene i tuoi dati, sia esso un buffer di vertice, un buffer costante o altro.

I buffer costanti funzionano allo stesso modo dei buffer dei vertici e di altri tipi di buffer. Vale a dire, i dati in essi contenuti non sono accessibili dalla GPU fino a quando non esegue effettivamente il rendering del frame, quindi il buffer deve rimanere valido fino a quando la GPU non ha terminato. È necessario eseguire il doppio buffer di ciascun buffer costante, quindi è necessario aggiornare una copia per il fotogramma successivo e una copia per la GPU da leggere durante il rendering del fotogramma corrente. Questo è simile a come si farebbero buffer di vertici dinamici per un sistema di particelle o simili.

La register(cb0), register(cb1)impostazioni nel corrispondono HLSL agli slot VSSetConstantBuffers. Quando aggiorni le costanti per fotogramma che faresti VSSetConstantBuffers(0, 1, &pBuffer)per impostare CB0 e quando aggiorni quelle costanti per oggetto, dovresti VSSetConstantBuffers(1, 1, &pBuffer)impostare CB1. Ogni chiamata aggiorna solo i buffer a cui fanno riferimento i parametri start / count e non tocca gli altri.

Non è necessario associare il buffer per aggiornarlo con UpdateSubresource. In effetti, non dovrebbe essere vincolato quando lo si aggiorna, o questo potrebbe costringere il driver a fare copie di memoria extra internamente (consultare la pagina MSDN per UpdateSubresource, in particolare le osservazioni sulla contesa su una pagina in basso).

Non sono sicuro di cosa intendi con "Come fa D3D11 a sapere quale buffer voglio aggiornare o mappare?" Aggiorna o mappa quello di cui hai passato il puntatore.


3

Sembra esserci molta confusione sull'argomento della necessità di ricollegare i buffer costanti dopo averli aggiornati. Mentre sto imparando questo, ho visto molti argomenti e discussioni con opinioni opposte su questo. Vale a dire la migliore risposta qui, raccomandando di chiamare XXSetConstantBuffersdopo l'aggiornamento tramite UpdateSubresourceo Map/Unmap.

Inoltre, alcuni esempi e documentazione D3D MSDN sembrano utilizzare questo modello, vincolando (chiamando XXSetConstantBuffers) su un frame o anche su base di oggetti disegnati, anche se aggiornano solo un buffer esistente e non cambiano uno slot specifico con un buffer completamente diverso .

Penso che il peggior malinteso sia che in XXSetConstantBuffersrealtà "invia i dati precedentemente aggiornati alla GPU o li notifica dell'aggiornamento, in modo che prenda i nuovi valori - il che sembra essere completamente sbagliato.

In effetti, quando si utilizza UpdateSubresourceo Map/Unmap, la documentazione afferma che la GPU può eseguire più copie internamente se necessita ancora dei vecchi dati, ma questo non è un problema per l'utente dell'API quando si tratta di aggiornare un buffer già associato. Quindi la necessità di non essere esplicitamente slegati sembra superflua.

Durante la mia sperimentazione, sono giunto alla conclusione che non è necessario ricollegare i buffer XXSetConstantBuffersdopo averli aggiornati, a meno che non siano già associati! Fintanto che usi gli stessi buffer (condivisi tra shader, livelli di pipeline uniformi) che vengono associati (ad esempio nella fase di avvio), non è necessario ricollegarli, basta aggiornarli.

Qualche codice per mostrare meglio la natura dei miei esperimenti:

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

Ecco alcuni argomenti da Internet (forum di gamedev) che sembrano adottare e raccomandare questo approccio: http://www.gamedev.net/topic/649410-set-constant-buffers-every-frame/?view=findpost&p=5105032 e http://www.gamedev.net/topic/647203-updating-constant-buffers/#entry5090000

Per concludere, sembra davvero che l' associazione non sia necessaria a meno che non si modifichi completamente il buffer, ma fintanto che si condividono i buffer e il loro layout tra shader (pratica consigliata) in questi casi dovrebbe essere fatto:

  • All'avvio - associazione iniziale - ad esempio dopo aver creato il buffer.
  • Se è necessario / è stato progettato per utilizzare più di un buffer associato a uno slot specifico di uno o più stadi.
  • Dopo aver cancellato lo stato di deviceContext (durante il ridimensionamento di buffer / windows)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.