Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can one Constant Buffer be used for many Objects?

I am new to Direct3D 11 and I am having some trouble understanding how to update constant (and other buffers) on a per-object basis. I some simple code where I am trying to get two Quads to draw to the screen, but at different positions. Here is the code I am using to draw them.

// ------------------------------------------------------------------------------------------------------------------------------
void QuadShape::UpdateBuffers(ID3D11DeviceContext* pContext)
{
  // We need to know about our verts + set the constant buffers...
  // NOTE: We only really need to do this when the buffer data actually changes...
  XMMATRIX translate = XMMatrixTranspose(XMMatrixTranslation(X, Y, 0));
  XMStoreFloat4x4(&ConstData.World, translate);

  D3D11_MAPPED_SUBRESOURCE mappedResource;
    ZeroMemory(&mappedResource, sizeof(D3D11_MAPPED_SUBRESOURCE));

  pContext->Map(ConstBuf, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
  memcpy(mappedResource.pData, &ConstData, sizeof(ObjectConstBuffer));
  pContext->Unmap(ConstBuf, 0);

}

// ------------------------------------------------------------------------------------------------------------------------------
void QuadShape::Draw(ID3D11DeviceContext* pContext)
{
  UpdateBuffers(pContext);
  pContext->DrawIndexed(_VertCount, _StartVert, 0);
}

You can see that I am computing a translation matrix based on the object's current X/Y position, and mapping that to the object's constant buffer, denoted as 'ConstBuf'. The problem that I am having is coming from the fact that all of the quads are ending up being drawn at the same position even though I have verified that the matrices computed for each are indeed different, and that the buffers are indeed dynamic.

I am guessing that what is happening is that the mapped resource is just being overwritten with whatever the last matrix is, but I thought that MAP_WRITE_DISCARD was supposed to avoid this. I am confused, how can I use a different constant buffer per object, and get them to show up at a different position?

like image 893
A.R. Avatar asked Oct 15 '25 07:10

A.R.


1 Answers

You should group your constant buffers by update frequency, so if you have some data that changes per object, put that in one constant buffer. If you have other data - like a projection matrix - that changes only when the window is resized, put that in another constant buffer.

So, for example, define two such buffers like so:

struct CBPerObject
{
    XMMATRIX mWorld;    // world matrix
};

struct CBChangeOnResize
{
    XMMATRIX mProjection;     // projection matrix
};

Then create the constant buffers and keep a reference to them in member variables :

CComPtr<ID3D11Buffer> m_pCBPerObject;        // dx11 constant buffer (per object)
CComPtr<ID3D11Buffer> m_pCBChangeOnResize;   // dx11 constant buffer (change on resize)

Creation code (error handling omitted for clarity) :

// create the constant buffers
D3D11_BUFFER_DESC pBuffDesc;
ZeroMemory(&pBuffDesc, sizeof(pBuffDesc));
pBuffDesc.Usage = D3D11_USAGE_DEFAULT;
pBuffDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
pBuffDesc.CPUAccessFlags = 0;

// per object changes
pBuffDesc.ByteWidth = sizeof(CBPerObject);
m_pDevice->CreateBuffer(&pBuffDesc, nullptr, &m_pCBPerObject);

// on resize changes
pBuffDesc.ByteWidth = sizeof(CBChangeOnResize);
m_pDevice->CreateBuffer(&pBuffDesc, nullptr, &m_pCBChangeOnResize);

Now you can bind them just once, during initialisation (assuming the layout will not change):

// constant buffers never change in shaders
pContext->VSSetConstantBuffers(0, 1, &m_pCBPerObject.p);
pContext->VSSetConstantBuffers(1, 1, &m_pCBChangeOnResize.p);

You may need to bind to the pixel shader too, but using PSSetConstantBuffers instead.

Now, you only need to update the constant buffers when required. For example, when the window is resized:

void CMyClass::OnSize()
{
    // update projection matrix
    CBChangeOnResize cbBuffer;

    // calculate the projection matrix - for example:
    XMMATRIX mProjection = XMMatrixOrthographicOffCenterLH(fLeft, fRight, fBottom, 
                                                           fTop, 0.1f, 1000.0f);
    cbBuffer.mProjection = XMMatrixTranspose(mProjection);

    // update the constant buffer
    pContext->UpdateSubresource(m_pCBChangeOnResize, 0, nullptr, &cbBuffer, 0, 0);
}

Similarly, when drawing, update the world matrix for each object:

void CMyClass::DrawScene()
{
    // draw the complete scene
    CBPerObject cbBuffer;

    // ... clear render target etc. 

    // for each object ... 
    {
        cbBuffer.mWorld = XMLoadFloat4x4(pObject->GetWorld());

        // update cb and draw object
        pContext->UpdateSubresource(m_pCBPerObject, 0, nullptr, &cbBuffer, 0, 0);
        pContext->DrawIndexed(6, 0, 0);
    }

    // ... etc.
}

So there is no need to re-bind the constant buffers unless the layout changes, and you can use UpdateSubresource instead of Map / Unmap as I've shown above.

In your vertex (pixel) shader, define the constant buffers according to the slot on which they were bound:

cbuffer cbPerObject : register (b0)
{
    matrix mWorld;
};

cbuffer cbChangeOnResize : register (b1)
{
    matrix mProjection;
};

In the syntax : register (b0) and register (b1) the b0 and b1 refer to the first argument supplied to VSSetConstantBuffers.

like image 136
Roger Rowland Avatar answered Oct 16 '25 21:10

Roger Rowland



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!