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?
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With