5
\$\begingroup\$

I am trying to optimise some of my rendering to get is useable on lower end machines (in my case a 2GHz i5 laptop with a GT 520M).

A quick investigation showed that the GPU isnt actually getting to 100%, but both CPU cores are. While most of this is non-rendering stuff (which I can likely make faster), I was surprised to find the piece of code below is apparently using 10% (20% of a core).

void RenderStaticTerrain()
    SetShadersInputLayoutConstantsEtc();
    for region in regionsInView: //about 20
        region->SetVertexAndIndexBuffer()
        for texture in regoin->UsedTextures(): //around 100
            d3dContext->SetAsShaderResource(texture);
            d3dContext->DrawIndexed(...);

I used the Windows Performance Analyzer sample based profiling and that points at some AMD or Nvidia DLL's as being where the CPU spends its time, largely I believe within the DrawIndexed (found via QueryPerformanceCounter. The sampling results seemed to dump the dll time just in RenderStaticTerrain itself).

The textures, buffers, etc are all immutable usage so don't see anything obvious to consume CPU there like moving vertices about or anything like that.

Two idea I have which (both of which look like some work) is texture arrays and using a few very large textures containing the others. However I am not sure how viable either of those are, and if it would actually make any performance difference.

For texture arrays it seems I can have upto 2048 which is plenty. However I am not sure if the GPU is going happy with what seems to me like effectively random access via a vertex buffer field. The major problem however is it appears all the textures need to have the same size, while most of mine are in the 32x32 to 128x128 range, with some even larger ones.

Another idea was to pack them into some really big textures, but this always seems to cause problems with mip-mapping and texture filtering, since even if I repeat each textures border pixel a few times, a few mip levels down and I have one pixel in the small texture being made from pixels of unrelated textures.

Not sure what other options there are. I am still not really sure why that loop uses much CPU at all.

The current code is only D3D11 targeting D3D11 hardware, but I do plan to support older hardware and OpenGL.

\$\endgroup\$
1
  • \$\begingroup\$ +1 for having profiling data! :) Texture Atlases are fairly routine 'improve the speed of things' approaches. If you draw the whole scene with just five textures, say, does the fps/CPU-usage improve? No need to implement texture atlases until you see the supposed gains. It might all be about counting 'batches'. \$\endgroup\$
    – Will
    Jul 9, 2013 at 13:31

1 Answer 1

4
\$\begingroup\$

You've about 2,000 draw calls in there, but draw call overhead is much reduced in D3D11 (by comparison to 9) - they're still not free, of course, but the old days of horror stories from excessive draw calls are definitely over and any reasonable setup from the past 6 or so years should be able to handle this kind of count with ease (yes, I've profiled this; it's not that big a problem).

A bigger problem is the way you're traversing and sorting your scene. Having a separate vertex/index buffer pair for each region in your scene means that you're getting a LOT of state changes - when taken together with textures you're also looking at about 2,000 state changes per frame. Now that's going to hurt you somewhat more.

The first solution I'd recommend is that instead of separate vertex/index buffers per region, you just put everything into a single big vertex/index buffer pair. Then use the StartIndexLocation parameter of DrawIndexed to specify which subset to draw. That will get rid of those state changes and give you a single ISSetVertexBuffers/IASetIndexBuffer pair per frame.

From there you can look at sorting your entire scene by texture. So instead of drawing immediately as you traverse a region, you instead store the information you need to draw (i.e. a pointer to your texture and your DrawIndexed parameters) in a container of some sort, then sort the resulting list by texture. Finally walk through the list and issue texture changes as required followed by groups of draw calls per-texture.

The resulting pseudo-code might be structured something like this:

void RenderStaticTerrain()
    ClearListOfDrawCalls();
    for region in regionsInView: //about 20
        for texture in region->UsedTextures(): //around 100
            AddTextureAndDrawCallParamsToList();

    SetShadersInputLayoutConstantsEtc();
    SetVertexAndIndexBuffer();

    for texture in ListOfStuff
        d3dContext->SetAsShaderResource(texture.texture);
        for drawCall in texture.drawCalls
            d3dContext->DrawIndexed(drawCall.indexCount, drawCall.startIndex, 0);

That should get your state change overhead down to an absolute minimum, and allow the D3D runtime and driver to better optimize multiple successive draw calls that share the same state (which it can't do in your current setup).

If you still have performance problems after making these changes then is time to start looking at some more complex batching.

\$\endgroup\$
1
  • \$\begingroup\$ Well the second bit is easy (the current implementation actually has a startIndex & indexCount for every texture used by the static terrain. For sure I can merge what I have right now. Looking forward is their a practical way with a single buffer to have not-quite static terrain (say load/unload regoins to allow for a larger world)? Also out of interest, do you actually know why it should be expensive CPU side? Regardless of the number of state changes isn't it just a couple of small commands to feed the GPU with the cost being there? \$\endgroup\$
    – Will
    Jul 9, 2013 at 19:17

You must log in to answer this question.

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