13
\$\begingroup\$

Alright, I'm having a hard time grasping how constant buffers are bound to a pipeline stage and updated. I understand that DirectX11 can have up to 15 shader-constant buffers per stage and each buffer can hold up to 4096 constants. However, I don't understand whether the ID3D11Buffer COM used to interact with the constant buffers is just a mechanism (or handle) used for fill these buffer slots or if the object actually references a particular instance of buffer data that is pushed back and forth between the GPU and CPU.

I think my confusion on the topic is the cause of a problem I'm having using two different constant buffers.

Here is some example shader code.

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

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

The way my code is organized, the camera will be handling updating the relevant per frame data and GameObjects will be updating their own per object data. Both classes have their own ID3D11Buffer that is used to do this (Using a hub architecture, so one GameObject class will handle the rendering of all the instanced GameObjects in the world).

The problem is I can only get one updated at a time, depending on the slot and I presume the update order one buffer gets filled while the other gets zero'd out.

This is essentially my code. Both classes use identical update logic.

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

My main questions are -

  • Do I need to set or bind the ShaderBuffer in order to update it with the UpdateSubresource call? (Meaning manipulate it only when it is in the pipeline) Or is it a blob of data that will be sent in with the VSSetConstantBuffer call? (Meaning the order of binding and updating data doesn't matter, I can update it in the pipeline or somehow on the cpu)
  • When setting or binding the buffer, do I need to reference slot 0 to update the PerFrame buffer and slot 1 to update the PerObject buffer? Could some kind of confusion with this call in my code cause all the buffers to be overwritten?
  • How does D3D11 know which buffer I want to update or map? Does it know from the ID3D11Buffer COM used?

Edit -

Changed the constant buffer register tags in the example above. Using (cb#) instead of (b#) was affecting the buffers from updating correctly for some reason. Not sure where I picked up the original syntax or if it's valid at all, but it appears to of been my main problem.

\$\endgroup\$

2 Answers 2

18
\$\begingroup\$

The ID3D11Buffer references an actual chunk of memory that holds your data, whether it's a vertex buffer, constant buffer, or whatever.

Constant buffers work the same way as vertex buffers and other kinds of buffers. Namely, the data in them isn't accessed by the GPU until it actually renders the frame, so the buffer has to remain valid until the GPU is done with it. You should double-buffer each constant buffer, so you have one copy to update for the next frame, and one copy for the GPU to read while rendering the current frame. This is similiar to how you would do dynamic vertex buffers for a particle system or suchlike.

The register(cb0), register(cb1) settings in the HLSL correspond with the slots in VSSetConstantBuffers. When you update the per-frame constants you'd do VSSetConstantBuffers(0, 1, &pBuffer) to set CB0 and when you update the per-object ones you'd do VSSetConstantBuffers(1, 1, &pBuffer) to set CB1. Each call updates only the buffers referred to by the start/count parameters, and doesn't touch the others.

You do not need to bind the buffer to update it with UpdateSubresource. In fact, it shouldn't be bound when you update it, or this may force the driver to make extra memory copies internally (see the MSDN page for UpdateSubresource, notably the remarks on contention about a page down).

I'm not sure what you mean by "How does D3D11 know which buffer I want to update or map?" It updates or maps the one whose pointer you passed in.

\$\endgroup\$
0
3
\$\begingroup\$

There seems to be a lot of confusion around the topic of needing to re-bind constant buffers after updating them. As I'm learning about this myself I have seen a lot of topics and discussions with opposite opinions on this. Namely the best answer here, recommending calling XXSetConstantBuffers after you update via UpdateSubresource or Map/Unmap.

Also, some D3D MSDN samples and documentation seem to use this pattern, binding (calling XXSetConstantBuffers) on a per frame or even per drawn-object basis, even though they only update an existing buffer, and not change a specific slot with a completely different buffer.

I think the worst misconception is that XXSetConstantBuffers actually "sends the data you previously updated to the GPU or notifies it of the update, so that it takes the new values - which seems to be completely wrong.

Indeed, when using UpdateSubresource or Map/Unmap, documentation states that internally multiple copies may be made by the GPU if it still needs the old data, but this is not a concern for the user of the API when it comes to updating an already bound buffer. Hence the need to explicitly unbound seems superfluous.

During my experimentation, I have come to the conclusion that it is unnecessary to re-bind buffers via XXSetConstantBuffers after updating them, unless they are not already bound! As long as you use the same buffers (shared between shaders, eveny pipeline stages) which are once bound (at the start-up phase, for example), you don't need to re-bind them - just update them.

Some code to better showcase my experiments nature:

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

Here are some topics from the internet (gamedev forums) which seem to adopt and recommend this approach: http://www.gamedev.net/topic/649410-set-constant-buffers-every-frame/?view=findpost&p=5105032 and http://www.gamedev.net/topic/647203-updating-constant-buffers/#entry5090000

To conlcude, it really seems that binding is not needed unless you change the buffer entirely but as long as you share buffers and their layout between shaders (recommended practice) binding should be done in these cases:

  • At start-up - initial binding - after creating the buffer for example.
  • If you need/have designed to use more than one buffer bound to a specific slot of one or more stages.
  • After clearing state of the deviceContext (when resizing buffers/windows)
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .