0
\$\begingroup\$

I'm trying to refresh my DirectX11 knowledge, and ran into a problem while trying to render multiple meshes instanced.

What I want to do is give my ObjectManager class the name of an .obj file and it then parses the vertex/index data out of that file. If I try to load the same .obj more than once, it should instead add an instance at the desired position in the scene. To do so I have a Vector storing all the Data for the objects. (see code below)

My problem is a massive performance drop when iterating though the vector of mesh info instead of just rendering the first object in the vector (for test purposes). Now, this sounds obvious of course, what I'm struggling to understand is, that this performance drop happens even when the vector only contains 1 element. So simply adding the for loop seems to cause this , even without rendering more or even effectively iterating through a bunch of items. (it drops from 2000+ fps to around 300, plus a very regular lag while moving. This is so substantial with so little change that I must assume something funny is going on).

So my assumption is either:

  1. Vector is not a good way to store the mesh data and render all of it every frame. Is a vector not the common, or at least a feasible way, of storing this kind of data?
  2. I might be doing something wrong while creating/updating the buffers for every object.

ObjectManager should hold all the objects i spawn in the scene, and in it's render function it will render all objects I have registered with it. To reduce draw calls I want to instance multiple calls of the same object, so my idea was to have a struct like this:

struct MeshDataBundle { std::string name; //used as ID to not spawn multiple struct with same Model std::shared_ptr<Model> model; std::vector<Instance> instances; //Instance = struct with just XMFLOAT3 for position };

My ObjectManager class now has a std::vector of this struct. In the render function, it should iterate though the vector and render the Model in it with the instance information in the included instances vector.

The render function looks like this:

//D3D class, passed as paramter, has the Device, DeviceContext etc.
public void ObjectManager::render(D3D& d3d)
{
    //called only the first time
    if (!initialized)
    {
        initBuffers(d3d);
    }

    //run through all models
    for (auto val : modelData)
    {
        //update the subresources and buffers
        DirectX::XMMATRIX tempWorld;
        tempWorld = XMLoadFloat4x4(&val.model->getWorld());
        //cbPerObject is a struct with only a XMMATRIX world world-matrix
        m_cbPerObject.world = DirectX::XMMatrixTranspose(tempWorld);


       //Update Buffers with vertices and instance information. Using CComPtr's, thus the "..Buffer.p". 
       //Dynamic buffers bc updated multiple times per frame
        D3D11_MAPPED_SUBRESOURCE mappedResource = { 0 };
        d3d.DevCon->Map(cbPerObjectBuffer.p, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
        memcpy(mappedResource.pData, &m_cbPerObject, sizeof(cbPerObject));
        d3d.DevCon->Unmap(cbPerObjectBuffer.p, 0);


        D3D11_MAPPED_SUBRESOURCE mappedResourceInst = { 0 };
        d3d.DevCon->Map(cbInstanceBuffer.p, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResourceInst);
        memcpy(mappedResourceInst.pData, &val.instances[0], sizeof(Instance)*val.instances.size());
        d3d.DevCon->Unmap(cbInstanceBuffer.p, 0);



        d3d.DevCon.p->VSSetConstantBuffers(0, 1, &cbPerObjectBuffer.p);

        //model class has its own vertex and index buffer, which we get and use
        CComPtr<ID3D11Buffer> bufferPointers[2] = { val.model->getVertexBuffer(), cbInstanceBuffer.p };

        UINT32 strides[2] = { sizeof(Vertex), sizeof(Instance) };
        UINT32 offsets[2] = { 0, 0 };

        d3d.DevCon.p->IASetVertexBuffers(0, 2, &bufferPointers[0].p, strides, offsets);

        d3d.DevCon.p->IASetIndexBuffer(val.model->getIndexBuffer(), DXGI_FORMAT_R32_UINT, 0);
        d3d.DevCon.p->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

        d3d.DevCon.p->DrawIndexedInstanced(val.model->getIndexCount(), val.instances.size(), 0, 0, 0);

    }
};

MISC:

Things I suppose might also be of relevance:

1.Creating the buffers in the ObjectManager (the class containing the above)

D3D11_BUFFER_DESC cbbd;
ZeroMemory(&cbbd, sizeof(D3D11_BUFFER_DESC));

cbbd.Usage = D3D11_USAGE_DYNAMIC;
cbbd.ByteWidth = sizeof(cbPerObject);
cbbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
cbbd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
cbbd.MiscFlags = 0;

hr = d3d.Device.p->CreateBuffer(&cbbd, NULL, &cbPerObjectBuffer);


D3D11_BUFFER_DESC cbbd2;
ZeroMemory(&cbbd2, sizeof(D3D11_BUFFER_DESC));

cbbd2.Usage = D3D11_USAGE_DYNAMIC;
cbbd2.ByteWidth = sizeof(Instance)*maxInstanceBufferSize;//largest possibly needed buffer for instances
cbbd2.BindFlags = D3D11_BIND_VERTEX_BUFFER;
cbbd2.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
cbbd2.MiscFlags = 0;

D3D11_SUBRESOURCE_DATA instanceData;
instanceData.pSysMem = &modelData[largestIndex].instances[0];
instanceData.SysMemPitch = 0;
instanceData.SysMemSlicePitch = 0;

hr = d3d.Device.p->CreateBuffer(&cbbd2, &instanceData, &cbInstanceBuffer);

2.The Model class has it's own vertex and index buffers. These are used in the above rendering function via getter functions. The Buffers of every Model are created like this:

vertexCount = v.size();
indexCount = i.size();

//VERTEX BUFFER
vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
vertexBufferDesc.ByteWidth = sizeof(Vertex) * vertexCount;
vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vertexBufferDesc.CPUAccessFlags = 0;
vertexBufferDesc.MiscFlags = 0;
vertexBufferDesc.StructureByteStride = 0;

vertexData.pSysMem = &v[0];
vertexData.SysMemPitch = 0;
vertexData.SysMemSlicePitch = 0;

result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &vertexBuffer);


// INDEX BUFFER
// Set up the description of the static index buffer.
indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
indexBufferDesc.ByteWidth = sizeof(UINT32) * indexCount;
indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
indexBufferDesc.CPUAccessFlags = 0;
indexBufferDesc.MiscFlags = 0;
indexBufferDesc.StructureByteStride = 0;

// Give the subresource structure a pointer to the index data.
indexData.pSysMem = &i[0];
indexData.SysMemPitch = 0;
indexData.SysMemSlicePitch = 0;

result = device->CreateBuffer(&indexBufferDesc, &indexData, &indexBuffer);

So, sorry for the very long post, but hopefully all info needed is in there.

\$\endgroup\$
  • \$\begingroup\$ Measure time in milliseconds/frame, not frames/second. Your loop copies the elements when iterating, consider auto& val. \$\endgroup\$ – Lars Viklund Jan 25 '16 at 3:00
  • \$\begingroup\$ good point about measuring, I will change that. And you were absolutely correct, auto& resolved the massive performance drop, thanks. I guess I can't upvote or accept it as long as it's only a comment, but the auto& was the issue. \$\endgroup\$ – Throzen Jan 25 '16 at 8:46
1
\$\begingroup\$

The primary thing that stands out is that your loop copy initializes the auto val loop variable with the elements as you iterate.

Even though your type is reasonably small, there's a bunch of overhead in doing so and it's likely that you would gain some by going for auto& val.

Of secondary concern is that you're using strings for non-textual things. Consider using integers or pointers as keys. If you need an actual printable string, you can have a string repository on the side from which you can register and look up strings with IDs.

\$\endgroup\$
  • \$\begingroup\$ I wanted to use the model itself as key at first, but had problems since I would need deduce the actual model somehow (a "Model" just parses Vertex and index info from .obj file, so the class itself doesn' say which model it contains atm). That said I wasn't aware that using string would be a noteworthy thing so it was not a big consideration to be honest, it seemed quite straight forward. now that I know it is bad design (at least) I will change it. \$\endgroup\$ – Throzen Jan 25 '16 at 16:30
  • \$\begingroup\$ There's some subtle tradeoffs in using strings as keys. Comparisons may need to look at all the characters. Hashing must consider all characters of the string. Sometimes it's worth to bake your identifiers beforehand or trade strings up-front for identifiers. \$\endgroup\$ – Lars Viklund Jan 25 '16 at 16:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.