4

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?

A.R.
  • 15,405
  • 19
  • 77
  • 123

3 Answers3

12

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.

Roger Rowland
  • 25,885
  • 11
  • 72
  • 113
  • Yep, I actually have been using one constant buffer per object, and binding it when each object is created. Noob mistake! – A.R. Mar 05 '14 at 21:23
  • @A.R. That's what I thought you were doing - you need just one cbuffer for per-object data, which you update before drawing each object. – Roger Rowland Mar 06 '14 at 06:07
  • 1
    Yeah, I thought the other suggestions of binding per call weren't right based on the MS docs. I think you may be the only person who read the code in the question. – A.R. Mar 06 '14 at 14:42
0

You need to update the constant buffer inside your shader. At the end of your UpdateBuffers method add:

pContext->VSSetConstantBuffers(0, 1, ConstBuf);

Also make sure your buffers are 16-byte aligned: http://msdn.microsoft.com/en-us/library/bb509632%28v=vs.85%29.aspx

The performance penalty isn't probably as big as you may imagine, especially compared with other expensive operations you are doing on per frame basis (I'll actually test it out of curiosity and let you know the results), and this is pretty standard way of doing it. If you don't want to take my word for it have a look at this article by Frank Luna, which, among other things, explains constant buffers pretty well and also provides common strategies for dealing with them:

http://d3dcoder.net/Data/Resources/d3d11Metro.pdf

If you are really worried about calling *SetConstantBuffers on per object basis (e.g. you have a lot of independently moving objects) then you can bind it once per frame and then use a pointer to the bound buffer for each object.

One way is to use a Shader class for that purpose which encapsulates all shader related resources as well as drawing the objects, so in my main update method I'd call Shader::bindBuffers() once and then call Shader::Render(Object*) for each object together with its parameters, including matrices. Inside Shader::Render() I use the buffers bound before and update them with map/unmap etc.

Another approach is to let objects draw themselves, as you do, but pass a pointer to the constant buffer, stored as a member of the calling class, to the render method (so UpdateBuffers in your case). You can see a similar approach in this sample from Microsoft:

http://code.msdn.microsoft.com/windowsapps/Metro-style-DirectX-7c64aa8d/sourcecode?fileId=50992&pathId=642179348

However these are optimisations, and you shouldn't generally worry about them prematurely.

jaho
  • 4,852
  • 6
  • 40
  • 66
  • Can you elaborate a little bit? It was my understanding that you should not have to bind the buffers each time since map/updatesubresource is supposed to take care of this. Also, isn't binding the bufer per object very expensive ? – A.R. Mar 05 '14 at 12:07
  • You're right that it's relatively expensive, which is why, ideally, you should have your constant buffers grouped by the frequency they are being used. The `MAP_WRITE_DISCARD` flag instructs the hardware to use the discarded buffer while you write to the newly allocated one, which then needs to be bound by the above command. – jaho Mar 05 '14 at 13:55
  • Are you sure about that? So if I have 100 objects, or 1000 objects, each that have their own world matrix I am just out of luck and have to take the penalty? – A.R. Mar 05 '14 at 15:59
  • That doesn't really answer the question. What about if there are 100's of objects. If there is a binding penalty, wouldn't this be a bad approach ? – A.R. Mar 05 '14 at 19:23
  • @A.R. It's a bad approach to update-bind-draw per object each frame. And it's a most simple approach. There are other approaches. But your question asks *exactly* about constant buffer per object, so you got answers about it. – Ivan Aksamentov - Drop Mar 05 '14 at 20:24
0

You must "update-bind-draw" for each object in this case. Pseudocode:

foreach(object in objects)
{
   constantBuffer.update(object.position);
   device.bind(constantBuffer);
   device.draw();
}

Your code is lacking bind calls.

This way, for N objects you got N buffer updates, N binds and N draw calls, which is horribly inefficient if you have many objects (you spent many time in driver, have CPU-GPU sync points, memory bandwidth is occupied by redundant data transfers). Though, unnecessary bind calls might be eliminated by decent driver implementation.

Update
There are several different approaches exist, which trades memory and code complexity to runtime performance. Notably, there are "Batching" and a family of techniques, called "Geometry Instancing":

1. Batching can be described as merging pre-transformed vertices of all objects in a vertex buffer, instead of transforming them in shader. You save vertex shader time considerably, but if any attribute of object (ex. position) changes, you need to re-calculate all vertices of object on CPU(!) and partially update vertex buffer. So, it works best for static objects (ex. that don't move often/at all) and small objects (not much vertices to update). Often used to draw 2D stuff: sprites, GUI and text.

2. Hardware geometry instancing. You need additional vertex buffer, which will store instance attributes instead of vertex attributes (we call it instance buffer). You must change input layout, adding per-instance attributes. You updating instance buffer as objects changing their attributes, just as you do it with any vertex buffer. You draw with DrawInstanced*() calls. It's a popular technique to optimize drawing of many dynamic objects, that are very similar (grass, leaves, meteorites, road traffic, fans on a football stadium, etc.). Several advanced tricks allows to apply it even to draw very different-looking objects.

3. Software geometry instancing. Implemented by hand Hardware geometry instancing. Can be used if your target hardware doesn't support hardware instancing (very old desktop and some mobile devices), of if you want some advanced control. You can just add an index attribute to every vertex, and use as instance information source either big constant buffer (for old devices) or texture or structures buffers (on newer devices), etc .

References:
Geometry instancing wiki
Chapter 3. Inside Geometry Instancing (warning: contains some old stuff)
rastertek.com - Tutorial 37: Instancing
braynzarsoft.net - Lesson 32: Direct3D 11 Instancing
NVIDIA Direct3D SDK 10 Code Samples: Instancing Tests, Skinned Instancing (download SDKs)
DirectX SDK sample "Instancing10"

Update
Note, that while instancing and batchin are awesome, it does not means that you must drop out constant buffers. As explained in Roger Rowland's answer, smart grouping of constants gives you good results in several cases, like immutable constants, constants shared by many objects, like camera's constants, etc. See "Don't Throw it All Away – Managing Buffers Efficiently" NVIDIA's presentaion on how exactly we tend to manage buffers

Ivan Aksamentov - Drop
  • 12,860
  • 3
  • 34
  • 61
  • Could you elaborate on the different approaches or give some links? I am really new to this and having a bit of a mental block in understanding it. Otherwise your answer has been very helpful so far. – A.R. Mar 05 '14 at 20:41
  • There is no need to bind again for each object - once the buffer is bound it stays bound. You only need to update its contents. – Roger Rowland Mar 05 '14 at 20:43
  • @RogerRowland You are 100% correct on this (I think I upvoted too soon). My main problem is that I am using a constant buffer per object, instead of updating a single constant buffer. – A.R. Mar 05 '14 at 21:22
  • @RogerRowland Exactly! This is one of the first optimizations I'd made. Next one is checking if update needed. There are huge field to optimize. Anyway, I've just wanted to show it's inefficiency and proposed completely different solution which might or might not be applicable to a concrete situation. – Ivan Aksamentov - Drop Mar 05 '14 at 23:13