0
\$\begingroup\$

Today my question is a about instance mesh performance in DX 11, the way I do my instancing is the standard way I think, its the same way I did it in XNA.

To start with each frame(every 2nd) I build a vertex stream filled with the world matrix for the mesh I want to instance, this second vertex stream is then set to the GPU with the mesh vertex buffer in slot 1, I have it set to InputClassification.PerInstanceData in the vertex element array.

Looks like this:

  Public Shared Elements As InputElement() = New InputElement() {New InputElement("POSITION", 0, DXGI.Format.R32G32B32_Float, 0, 0, InputClassification.PerVertexData, 0),
                                                              New InputElement("NORMAL", 0, DXGI.Format.R32G32B32_Float, InputElement.AppendAligned, 0, InputClassification.PerVertexData, 0),
                                                               New InputElement("BINORMAL", 0, DXGI.Format.R32G32B32_Float, InputElement.AppendAligned, 0, InputClassification.PerVertexData, 0),
                                                               New InputElement("TANGENT", 0, DXGI.Format.R32G32B32_Float, InputElement.AppendAligned, 0, InputClassification.PerVertexData, 0),
                                                                New InputElement("COLOR", 0, DXGI.Format.R32G32B32_Float, InputElement.AppendAligned, 0, InputClassification.PerVertexData, 0),
                                                                New InputElement("TEXCOORD", 0, DXGI.Format.R32G32_Float, InputElement.AppendAligned, 0, InputClassification.PerVertexData, 0),
                                                                 New InputElement("WORLD", 0, DXGI.Format.R32G32B32A32_Float, 0, 1, InputClassification.PerInstanceData, 1),
                                                                 New InputElement("WORLD", 1, DXGI.Format.R32G32B32A32_Float, InputElement.AppendAligned, 1, InputClassification.PerInstanceData, 1),
                                                                 New InputElement("WORLD", 2, DXGI.Format.R32G32B32A32_Float, InputElement.AppendAligned, 1, InputClassification.PerInstanceData, 1),
                                                                 New InputElement("WORLD", 3, DXGI.Format.R32G32B32A32_Float, InputElement.AppendAligned, 1, InputClassification.PerInstanceData, 1)
                                                         }

This setup works fine but I'm still getting a big frame time hit when I draw my trees, my trees are made with SpeedTree and are around 3k-10k triangles, some of them could be up to 5 draw calls per tree and that's why I'm using instance because I figure it has to be faster to instance it than to not, I mean I'm drawing the same thing over and over so isn't that why you use instancing?

For my world I have 8 types of tree and 5 variations of each and some trees have up to 5 meshes, I can only seem to render around 750-1250 instances per frame and keep it running smooth, btw that's total for all the trees not each type, I'm not sure of the tree type count because its random but ill ball park it to 100-150 of each tree type.

I draw the leafs with alpha test so I don't get early-z on the trunks too because I have no way of turning off alpha test for the trunks right now.

I know there are many things that will affect my frame times but as far as I can tell this is whats costing the most right now, with no trees I'm running at 90-110 fps and with trees I drop to 35-55. If I change the tree cull distance then of course I get more frames.

I have a bit of batching going on, first by Mesh name then by blend state.

I use a tile based deferred render, its based on AMD's code, I also have cascaded shadow maps with 3 splits, water reflections and sometimes dynamic cube map faces. So each mesh could end up getting drawn 6 times but I dont update the last shadow cascade every frame, I split the cube rendering over 6 frames and only render stuff that the water can see into the reflection target.

So I guess my question is am I doing instancing correct and I'm just trying to draw to much and need to work on LoD or am I doing it wrong? What do others get from there instancing systems?

Ps. 90-110 fps isn't for an empty world that's what I get for my full world but without the trees

\$\endgroup\$
0
\$\begingroup\$

Turns out I'm doing instancing the right way, there is another way but I have no need for that just yet and there is a slower way that uses a constant buffer to hold the instance data instead of a vertex buffer.

I was doing a few other things wrong that all stacked up to hinder me while rendering the trees, ill list them bellow.

While building the instance buffers I was having problems with map and unmap, I was using a data stream and that was making lots of garbage so the GC was having a bad time so I had it just make a new buffer on another thread every time I needed to update it but again the GC has issues with that much disposing, not so much the GC but more the frame spikes when it would kick in..... happened lots.

I did some digging and found the right way to do it is with map and unmap, grow the buffer if I need but instead of using a datastream I should use the function that returns a databox and the call write on the native pointer to that memory so I can avoid using data stream.

Looks like this:

           If ObjectCount > LastObjectCount Then
                If instancebuffer IsNot Nothing Then
                    instancebuffer.Dispose()
                    instancebuffer = Nothing
                End If
                instancebuffer = device.CreateBuffer(New BufferDescription(ObjectCount * 64, ResourceUsage.Dynamic, BindFlags.VertexBuffer, CpuAccessFlags.Write, 0, 0))
            End If

            Dim dbox As DataBox = _Context.MapSubresource(instancebuffer, 0, MapMode.WriteDiscard, MapFlags.None)
            Utilities.Write(dbox.DataPointer, EnityList, 0, ObjectCount)
            _Context.UnmapSubresource(instancebuffer, 0)
            newdata.DrawData = instancebuffer
            newdata.Count = ObjectCount
            LastObjectCount = ObjectCount
            dbox = Nothing

That was my first problem but that only got my 1/2 to where I wanted to be.

The next problem was not with the trees or the drawing of the trees but more the fact I was using a few massive shadow maps that were sucking 3ms out of my frame time.

I just dropped the size of the shadow map down and fixed it so I didn't have to use a massive one to have it look nice.

The first fix saved me 3ms and smoothed out the frame times because the GC was not running all the time and I wasn't recovering big chunks of native memory every few frames.

The second fix saved me another 1.5-2ms.

Yay up to a 5ms saving! I can draw more, so I did and that dropped the frames again but I had my predication testing to fix that.

On a final note I'm now using my first fix anywhere I need to map\unmap on the fly so I'm seeing an overall drop in GC and memory traffic. Good Times!!

\$\endgroup\$

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.